adding the memory debugging part

This commit is contained in:
Jidong Xiao
2023-09-18 23:44:04 -04:00
parent 86afd3a16d
commit 2457a6c6af

View File

@@ -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. - 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) - [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 freed (NOTE: delete calls free)
- Reading/writing off the end of mallocd blocks (NOTE: new calls malloc)
- Reading/writing inappropriate areas on the stack
- Memory leaks - where pointers to mallocd 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 <iostream>
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
Heres 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 heres 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 youve
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 dont 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")