diff --git a/lectures/06_memory/README.md b/lectures/06_memory/README.md index 480a9f2..d1686d3 100644 --- a/lectures/06_memory/README.md +++ b/lectures/06_memory/README.md @@ -148,6 +148,272 @@ for (int i = 0; i < rows; i++) { - Play this [animation](https://jidongxiao.github.io/CSCI1200-DataStructures/animations/dynamic_memory/two_d_array/index.html) to see what exactly the above code snippet does. -## 6.5 Exercises +## 6.5 Dynamic Allocation: Arrays of Class Objects + +We can dynamically allocate arrays of class objects. The default constructor (the constructor that takes no arguments) must be defined in order to allocate an array of objects. + +```cpp +class Foo { +public: + Foo(); + double value() const { return a*b; } +private: + int a; + double b; +}; + +Foo::Foo() { + static int counter = 1; + a = counter; + b = 100.0; + counter++; +} + +int main() { + int n; + std::cin >> n; + Foo *things = new Foo[n]; + std::cout << "size of int: " << sizeof(int) << std::endl; + std::cout << "size of double: " << sizeof(double) << std::endl; + std::cout << "size of foo object: " << sizeof(Foo) << std::endl; + for (Foo* i = things; i < things+n; i++) + std::cout << "Foo stored at: " << i << " has value " << i->value() << std::endl; + delete [] things; +} +``` + +The above program will produce the following output: + +```console +size of int: 4 +size of double: 8 +size of foo object: 16 +Foo stored at: 0x104800890 has value 100 +Foo stored at: 0x1048008a0 has value 200 +Foo stored at: 0x1048008b0 has value 300 +Foo stored at: 0x1048008c0 has value 400 +... +``` + +## 6.6 Exercises - [Leetcode problem 1480: Running Sum of 1d Array](https://leetcode.com/problems/running-sum-of-1d-array/). Solution: [p1480_runningsumofarray.cpp](../../leetcode/p1480_runningsumofarray.cpp) + +## 6.7 Memory Debugging + +In addition to the step-by-step debuggers like gdb, lldb, or the debugger in your IDE, we recommend using a memory +debugger like “Dr. Memory” (Windows, Linux, and MacOSX) or “Valgrind” (Linux and MacOSX). These tools can +detect the following problems: +- Use of uninitialized memory +- Reading/writing memory after it has been free’d (NOTE: delete calls free) +- Reading/writing off the end of malloc’d blocks (NOTE: new calls malloc) +- Reading/writing inappropriate areas on the stack +- Memory leaks - where pointers to malloc’d blocks are lost forever +- Mismatched use of malloc/new/new [] vs free/delete/delete [] +- Overlapping src and dst pointers in memcpy() and related functions + +## 6.8 Sample Buggy Program + +Can you see the errors in this program? + +```cpp +1 #include +2 +3 int main() { +4 +5 int *p = new int; +6 if (*p != 10) std::cout << "hi" << std::endl; +7 +8 int *a = new int[3]; +9 a[3] = 12; +10 delete a; +11 +12 } +``` + +## 6.9 Using Dr. Memory http://www.drmemory.org + +Here’s how Dr. Memory reports the errors in the above program: + +```console +~~Dr.M~~ Dr. Memory version 1.8.0 +~~Dr.M~~ +~~Dr.M~~ Error #1: UNINITIALIZED READ: reading 4 byte(s) +~~Dr.M~~ # 0 main [memory_debugger_test.cpp:6] +hi +~~Dr.M~~ +~~Dr.M~~ Error #2: UNADDRESSABLE ACCESS beyond heap bounds: writing 4 byte(s) +~~Dr.M~~ # 0 main [memory_debugger_test.cpp:9] +~~Dr.M~~ Note: refers to 0 byte(s) beyond last valid byte in prior malloc +~~Dr.M~~ +~~Dr.M~~ Error #3: INVALID HEAP ARGUMENT: allocated with operator new[], freed with operator delete +~~Dr.M~~ # 0 replace_operator_delete [/drmemory_package/common/alloc_replace.c:2684] +~~Dr.M~~ # 1 main [memory_debugger_test.cpp:10] +~~Dr.M~~ Note: memory was allocated here: +~~Dr.M~~ Note: # 0 replace_operator_new_array [/drmemory_package/common/alloc_replace.c:2638] +~~Dr.M~~ Note: # 1 main [memory_debugger_test.cpp:8] +~~Dr.M~~ +~~Dr.M~~ Error #4: LEAK 4 bytes +~~Dr.M~~ # 0 replace_operator_new [/drmemory_package/common/alloc_replace.c:2609] +~~Dr.M~~ # 1 main [memory_debugger_test.cpp:5] +~~Dr.M~~ +~~Dr.M~~ ERRORS FOUND: +~~Dr.M~~ 1 unique, 1 total unaddressable access(es) +~~Dr.M~~ 1 unique, 1 total uninitialized access(es) +~~Dr.M~~ 1 unique, 1 total invalid heap argument(s) +~~Dr.M~~ 0 unique, 0 total warning(s) +~~Dr.M~~ 1 unique, 1 total, 4 byte(s) of leak(s) +~~Dr.M~~ 0 unique, 0 total, 0 byte(s) of possible leak(s) +~~Dr.M~~ Details: /DrMemory-MacOS-1.8.0-8/drmemory/logs/DrMemory-a.out.7726.000/results.txt +``` + +And the fixed version: + +```console +~~Dr.M~~ Dr. Memory version 1.8.0 +hi +~~Dr.M~~ +~~Dr.M~~ NO ERRORS FOUND: +~~Dr.M~~ 0 unique, 0 total unaddressable access(es) +~~Dr.M~~ 0 unique, 0 total uninitialized access(es) +~~Dr.M~~ 0 unique, 0 total invalid heap argument(s) +~~Dr.M~~ 0 unique, 0 total warning(s) +~~Dr.M~~ 0 unique, 0 total, 0 byte(s) of leak(s) +~~Dr.M~~ 0 unique, 0 total, 0 byte(s) of possible leak(s) +~~Dr.M~~ Details: /DrMemory-MacOS-1.8.0-8/drmemory/logs/DrMemory-a.out.7762.000/results.txt +``` + +**Note**: Dr. Memory on Windows with the Visual Studio compiler may not report a mismatched free() / delete +/ delete [] error (e.g., line 10 of the sample code above). This may happen if optimizations are enabled and the +objects stored in the array are simple and do not have their own dynamically-allocated memory that lead to their +own indirect memory leaks. + +## 6.10 Using Valgrind http://valgrind.org/ + +And this is how Valgrind reports the same errors: + +```console +==31226== Memcheck, a memory error detector +==31226== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al. +==31226== Using Valgrind-3.9.0 and LibVEX; rerun with -h for copyright info +==31226== Command: ./a.out +==31226== +==31226== Conditional jump or move depends on uninitialised value(s) +==31226== at 0x40096F: main (memory_debugger_test.cpp:6) +==31226== +hi +==31226== Invalid write of size 4 +==31226== at 0x4009A3: main (memory_debugger_test.cpp:9) +==31226== Address 0x4c3f09c is 0 bytes after a block of size 12 alloc'd +==31226== at 0x4A0700A: operator new[](unsigned long) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==31226== by 0x400996: main (memory_debugger_test.cpp:8) +==31226== +==31226== Mismatched free() / delete / delete [] +==31226== at 0x4A07991: operator delete(void*) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==31226== by 0x4009B4: main (memory_debugger_test.cpp:10) +==31226== Address 0x4c3f090 is 0 bytes inside a block of size 12 alloc'd +==31226== at 0x4A0700A: operator new[](unsigned long) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==31226== by 0x400996: main (memory_debugger_test.cpp:8) +==31226== +==31226== +==31226== HEAP SUMMARY: +==31226== in use at exit: 4 bytes in 1 blocks +==31226== total heap usage: 2 allocs, 1 frees, 16 bytes allocated +==31226== +==31226== 4 bytes in 1 blocks are definitely lost in loss record 1 of 1 +==31226== at 0x4A06965: operator new(unsigned long) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==31226== by 0x400961: main (memory_debugger_test.cpp:5) +==31226== +==31226== LEAK SUMMARY: +==31226== definitely lost: 4 bytes in 1 blocks +==31226== indirectly lost: 0 bytes in 0 blocks +==31226== possibly lost: 0 bytes in 0 blocks +==31226== still reachable: 0 bytes in 0 blocks +==31226== suppressed: 0 bytes in 0 blocks +==31226== +==31226== For counts of detected and suppressed errors, rerun with: -v +==31226== Use --track-origins=yes to see where uninitialised values come from +==31226== ERROR SUMMARY: 4 errors from 4 contexts (suppressed: 2 from 2) +``` + +And here’s what it looks like after fixing those bugs: + +```console +==31252== Memcheck, a memory error detector +==31252== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al. +==31252== Using Valgrind-3.9.0 and LibVEX; rerun with -h for copyright info +==31252== Command: ./a.out +==31252== +hi +==31252== +==31252== HEAP SUMMARY: +==31252== in use at exit: 0 bytes in 0 blocks +==31252== total heap usage: 2 allocs, 2 frees, 16 bytes allocated +==31252== +==31252== All heap blocks were freed -- no leaks are possible +==31252== +==31252== For counts of detected and suppressed errors, rerun with: -v +==31252== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2) +``` + +## 6.11 How to use a memory debugger + +- Detailed instructions on installation & use of these tools are available here: +http://www.cs.rpi.edu/academics/courses/fall23/csci1200/memory_debugging.php +- Memory errors (uninitialized memory, out-of-bounds read/write, use after free) may cause seg faults, crashes, +or strange output. +- Memory leaks on the other hand will never cause incorrect output, but your program will be inefficient and +hog system resources. A program with a memory leak may waste so much memory it causes all programs on +the system to slow down significantly or it may crash the program or the whole operating system if the system +runs out of memory (this takes a while on modern computers with lots of RAM & harddrive space). +- For many future homeworks, Submitty will be configured to run your code with Dr. Memory to search for +memory problems and present the output with the submission results. For full credit your program must be +memory error and memory leak free! +- A program that seems to run perfectly fine on one computer may still have significant memory errors. Running +a memory debugger will help find issues that might break your homework on another computer or when +submitted to the homework server. +- **Important Note**: When these tools find a memory leak, they point to the line of code where this memory +was allocated. These tools does not understand the program logic and thus obviously cannot tell us where it +should have been deleted. +- A final note: STL and other 3rd party libraries are highly optimized and sometimes do sneaky but correct and +bug-free tricks for efficiency that confuse the memory debugger. For example, because the STL string class +uses its own allocator, there may be a warning about memory that is “still reachable” even though you’ve +deleted all your dynamically allocated memory. The memory debuggers have automatic suppressions for some +of these known “false positives”, so you will see this listed as a “suppressed leak”. So don’t worry if you see +those messages. + +## 6.12 Diagramming Memory Exercises + +- Draw a diagram of the heap and stack memory for each segment of code below. Use a “?” to indicate that the +value of the memory is uninitialized. Indicate whether there are any errors or memory leaks during execution +of this code. + +```cpp +class Foo { +public: + double x; + int* y; +}; +Foo a; +a.x = 3.14159; +Foo *b = new Foo; +(*b).y = new int[2]; +Foo *c = b; +a.y = b->y; +c->y[1] = 7; +b = NULL; +``` + +```cpp +int a[5] = { 10, 11, 12, 13, 14 }; +int *b = a + 2; +*b = 7; +int *c = new int[3]; +c[0] = b[0]; +c[1] = b[1]; +c = &(a[3]); +``` + +Write code to produce this diagram: + +[alt text](memory_exercise.png "memory exercise")