1、Fixing Memory Problems This chapter is about finding bugs in C/C+ programs with the help of a memory debugger. A memory debugger is a runtime tool designed to trace and detect bugs in C/C+ memory management and access. It does not replace a general debugger. In the following sections, we will descri
2、be the memory access bugs that typically occur in C/C+ programs, introduce memory debuggers, and show with two examples how these tools find bugs. We will then show how to run memory and source code debuggers together, how to deal with unwanted error messages by writing a suppression file, and what
3、restrictions need to be considered. 4.1 Memory Management in C/C+ Powerful but Dangerous The C/C+ language is able to manage memory resources, and can access memory directly through pointers. Efficient memory handling and “programming close to the hardware” are reasons why C/C+ replaced assembly lan
4、guage in the implementation of large software projects such as operating systems, where performance and low overhead play a major role. The allocation of dynamic memory (also known as heap memory) in C/C+ is under the control of the programmer. New memory is allocated with functions such as malloc()
5、 and various forms of the operator new. Unused memory is returned with free() or delete. The memory handling in C/C+ gives a large degree of freedom, control, and performance, but comes at a high price: the memory access is a frequent source of bugs. The most frequent sources of memory access bugs a
6、re memory leaks, incorrect use of memory management, buffer overruns, and reading uninitialized memory. 33 34 4 Fixing Memory Problems 4.1.1 Memory Leaks Memory leaks are data structures that are allocated at runtime, but not deallocated once they are no longer needed in the program. If the leaks ar
7、e frequent or large, eventually all available main memory in your computer will be consumed. The program will first slow down, as the computer starts swapping pages to virtual memory, and then fail with an out-of-memory error. Finding leaks with a general debugger is difficult because there is no ob
8、vious faulty statement. The bug is that a statement is missing or not called. 4.1.2 Incorrect Use of Memory Management A whole class of bugs is associated with incorrect calls to memory management: freeing a block of memory more than once, accessing memory after freeing it, or freeing a block that w
9、as never allocated. Also belonging to this class is using delete instead of delete for C+ array deallocation, as well as using malloc() together with delete, and using new together with free(). 4.1.3 Buffer Overruns Buffer overruns are bugs where memory outside of the allocated boundaries is overwri
10、tten, or corrupted. Buffer overruns can occur for global variables, local variables on the stack, and dynamic variables that were allocated on the heap with memory management. One nasty artifact of memory corruption is that the bug may not become visible at the statement where the memory is overwrit
11、ten. Only later, another statement in the program will access this memory location. Because the memory location has an illegal value, the program can behave incorrectly in a number of ways: the program may compute a wrong result, or, if the illegal value is in a pointer, the program will try to acce
12、ss protected memory and crash. If a function pointer variable is overwritten, the program will do a jump and try to execute data as program code. The key point is that there may be no strict relation between the statement causing the memory corruption and the statement triggering the visible bug. 4.
13、1.4 Uninitialized Memory Bugs Reading uninitialized memory can occur because C/C+ allows creation of variables without an initial value. The programmer is fully responsible to initialize all global and local variables, either through assignment statements or through the 4.2 Memory Debuggers to the R
14、escue 35 various C+ constructors. The memory allocation function malloc() and operator new also do not initialize or zero out the allocated memory blocks. Uninitialized variables will contain unpredictable values. 4.2 Memory Debuggers to the Rescue The above categories of memory access bugs created
15、a need for adequate debugging tools. Finding bugs related to leaked, corrupted, or uninitialized memory with a conventional debugger such as GDB turned out to be unproductive. To deal with memory leaks in large software projects, many programmers came up with the same idea. They created memory manag
16、ement functions/operators with special instrumentation to track where a memory block was allocated, and if each block was properly deallocated at the end of the program. Since everybody had the same memory bugs in their C/C+ programs, and since everybody improvised with custom instrumentation to tra
17、ck down at least some of these bugs, a market for a tool called memory debugger was created. The most wellknown tool is Purify, released in 1991 by Pure Software. Purifys name has since become synonymous with memory debugging. There is also Insure+, Valgrind, and BoundsChecker, among others. See the
18、 tools Appendix B.4 starting on page 198 for references and the survey in Luecke06 for a comparison of features. Memory debuggers do detailed bookkeeping of all allocated/deallocated dynamic memory. They also intercept and check access to dynamic memory. Some memory debuggers can check access to loc
19、al variables on the stack and statically allocated memory. Purify and BoundsChecker do this by object code instrumentation at program link time, Insure+ uses source code instrumentation, and Valgrind executes the program on a virtual machine and monitors all memory transactions. The code instrumenta
20、tion allows the tools to pinpoint the source code statement where a memory bug occurred. The following bugs are detectable by a memory debugger: Memory leaks Accessing memory that was already freed Freeing the same memory location more than once Freeing memory that was never allocated Mixing C mallo
21、c()/free()with C+ new/delete Using delete instead of delete for arrays Array out-of-bound errors Accessing memory that was never allocated Uninitialized memory read Null pointer read or write We will show in the next section how to attach a memory debugger to your program, and how the tool finds and
22、 reports bugs. 36 4 Fixing Memory Problems 4.3 Example 1: Detecting Memory Access Errors Our first example is a program that allocates an array in dynamic memory, accesses an element outside the final array element, reads an uninitialized array element, and finally forgets to deallocate the array. W
23、e use the public domain tool Valgrind on Linux as the memory debugger, and demonstrate how the tool automatically detects these bugs. This is the code of our program main1.c: 1 /* main1.c */ 2 #include 3 int main(int argc, char* argv) 4 const int size=100; 5 int n, sum=0; 6 int* A = (int*)malloc( sizeof(int)*size ); 7 8 for (n=size; n0; n-) /* walk through A100.A1 */ 9 An = n; /* error: A100 invalid write*/ 10 for (n=0;nsize; n+) /* walk through A0.A99 */ 11 sum += An; /* error: A0 not initialized*/ 12 printf (sum=%dn, sum); 13 return 0; /* mem leak: A */