C毕业设计外文翻译.doc
Fixing Memory ProblemsThis chapter is about finding bugs in C/C+ programs with the help of a memorydebugger. A memory debugger is a runtime tool designed to trace and detect bugsin C/C+ memory management and access. It does not replace a general debugger.In the following sections, we will describe the memory access bugs that typicallyoccur in C/C+ programs, introduce memory debuggers, and show with two exampleshow these tools find bugs. We will then show how to run memory and sourcecode debuggers together, how to deal with unwanted error messages by writing asuppression file, and what restrictions need to be considered.4.1 Memory Management in C/C+ Powerful but DangerousThe C/C+ language is able to manage memory resources, and can access memorydirectly through pointers. Efficient memory handling and “programming close to thehardware” are reasons why C/C+ replaced assembly language in the implementationof large software projects such as operating systems, where performance andlow overhead play a major role. The allocation of dynamic memory (also known asheap memory) in C/C+ is under the control of the programmer. New memory isallocated with functions such as malloc() 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, andperformance, but comes at a high price: the memory access is a frequent source ofbugs. The most frequent sources of memory access bugs are memory leaks, incorrectuse of memory management, buffer overruns, and reading uninitialized memory.3334 4 Fixing Memory Problems4.1.1 Memory LeaksMemory leaks are data structures that are allocated at runtime, but not deallocatedonce they are no longer needed in the program. If the leaks are frequent or large,eventually all available main memory in your computer will be consumed. The programwill 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 isdifficult because there is no obvious faulty statement. The bug is that a statement ismissing or not called.4.1.2 Incorrect Use of Memory ManagementA 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 was never allocated. Also belonging to this class is usingdelete instead of delete for C+ array deallocation, as well as usingmalloc() together with delete, and using new together with free().4.1.3 Buffer OverrunsBuffer overruns are bugs where memory outside of the allocated boundaries is overwritten,or corrupted. Buffer overruns can occur for global variables, local variableson the stack, and dynamic variables that were allocated on the heap with memorymanagement.One nasty artifact of memory corruption is that the bug may not become visibleat the statement where the memory is overwritten. Only later, another statement inthe program will access this memory location. Because the memory location has anillegal value, the program can behave incorrectly in a number of ways: the programmay compute a wrong result, or, if the illegal value is in a pointer, the program willtry to access 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. Thekey point is that there may be no strict relation between the statement causing thememory corruption and the statement triggering the visible bug.4.1.4 Uninitialized Memory BugsReading uninitialized memory can occur because C/C+ allows creation of variableswithout an initial value. The programmer is fully responsible to initializeall global and local variables, either through assignment statements or through the4.2 Memory Debuggers to the Rescue 35various C+ constructors. The memory allocation function malloc() and operatornew also do not initialize or zero out the allocated memory blocks. Uninitializedvariables will contain unpredictable values.4.2 Memory Debuggers to the RescueThe above categories of memory access bugs created a need for adequate debuggingtools. Finding bugs related to leaked, corrupted, or uninitialized memory witha conventional debugger such as GDB turned out to be unproductive. To deal withmemory leaks in large software projects, many programmers came up with the sameidea. They created memory management functions/operators with special instrumentationto track where a memory block was allocated, and if each block wasproperly deallocated at the end of the program.Since everybody had the same memory bugs in their C/C+ programs, and sinceeverybody improvised with custom instrumentation to track down at least some ofthese bugs, a market for a tool called memory debugger was created. The most wellknowntool is Purify, released in 1991 by Pure Software. Purifys name has sincebecome synonymous with memory debugging. There is also Insure+, Valgrind, andBoundsChecker, among others. See the tools Appendix B.4 starting on page 198 forreferences and the survey in Luecke06 for a comparison of features.Memory debuggers do detailed bookkeeping of all allocated/deallocated dynamicmemory. They also intercept and check access to dynamic memory. Somememory debuggers can check access to local variables on the stack and statically allocatedmemory. Purify and BoundsChecker do this by object code instrumentationat program link time, Insure+ uses source code instrumentation, and Valgrind executesthe program on a virtual machine and monitors all memory transactions. Thecode instrumentation allows the tools to pinpoint the source code statement where amemory 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 malloc()/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 writeWe will show in the next section how to attach a memory debugger to your program,and how the tool finds and reports bugs.36 4 Fixing Memory Problems4.3 Example 1: Detecting Memory Access ErrorsOur first example is a program that allocates an array in dynamic memory, accessesan element outside the final array element, reads an uninitialized array element, andfinally forgets to deallocate the array. We use the public domain tool Valgrind onLinux as the memory debugger, and demonstrate how the tool automatically detectsthese bugs. This is the code of our program main1.c:1 /* main1.c */2 #include <stdio.h>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 );78 for (n=size; n>0; n-) /* walk through A100.A1 */9 An = n; /* error: A100 invalid write*/10 for (n=0;n<size; n+) /* walk through A0.A99 */11 sum += An; /* error: A0 not initialized*/12 printf ("sum=%dn", sum);13 return 0; /* mem leak: A */14 We compile the program with debug information and then run under Valgrind:> gcc -g main1.c> valgrind -tool=memcheck -leak-check=yes ./a.outIn the following sections we go through the error list reported by Valgrind.4.3.1 Detecting an Invalid Write AccessThe first and perhaps most severe error is a buffer overrun: the accidental writeaccess to array element A100. Because the array has only 100 elements, thehighest valid index is 99. A100 points to unallocated memory that is locatedjust after the memory allocated for array A. Valgrind thus reports an “invalid write”error:=11323= Invalid write of size 4=11323= at 0x8048518: main (main1.c:9)=11323= Address 0x1BB261B8 is 0 bytes after a block=11323= of size 400 allocd=11323= at 0x1B903F40: malloc=11323= (in /usr/lib/valgrind/vgpreload_memcheck.so)=11323= by 0x80484F2: main (main1.c:6)The string "=11323=" refers to the process ID and is useful when Valgrind ischecking multiple processes 1. The important piece of information is that an invalid1 Valgrind will, per default, check only the first (parent) process that has been invoked. Use option-trace-children=yes to check all child processes as well.4.3 Example 1: Detecting Memory Access Errors 37write occurs in line 9 of main1.c. There is also additional information revealingthe address of the closest allocated memory block and how it was allocated. Thememory debugger guesses that the invalid write in line 9 is related to this memoryblock. The guess is correct because both belong to the same array A.Note that Valgrind is able to catch an out-of-array-bounds errors only when thearray is allocated as dynamic memory with malloc() or new. This is the case inthe example with the statement in line 6:6 int* A = (int*)malloc( sizeof(int)*size );If the example were instead written as int Asize in line 6, then A would bea local variable located on the stack and not on the heap. It turns out that Valgrinddoes not detect such an error but Purify is able to catch it. This shows that not allmemory debuggers will report exactly the same errors.修正内存问题本章是关于利用内存BUG调试器的帮助,来发现C或C+程序中的BUG。内存调试器是在C或C+程序管理或存储时用于追踪和发现BUG的运行时工具。它不能取代一个正规的调试器。在一下的章节,我们将描述几个代表性的在C或C+程序中处理的内容BUG,介绍内容调试器,并且介绍两个内存如何发现BUG的例子。我们然后将演示如何使内存与编码调试器一起运行,怎么去处理不需要的错误信息通过写文件,需要去考虑程序需要的限制。4.1 内存管理在C或C+中强健但是危险C或C+语言能够管理内存资源,并且通过指针直接读取内存。高效的内存处理和“直接控制硬件”是规划大型例如操作系统的软件工程取代汇编语言的原因,高性能和低限制是它作为一个重要角色的原因。动态内存的分配(众所周知的堆内存)在C或C+中是在程序员的控制之下的。新内存被分配担任起例如malloc()和各种有程序员构成的新的指针。不被使用的内存被free()或delete回收。4.1.1 内存泄漏内存泄漏是在运行时的内存分配结构,但是它们只需要我们分配一次,我们却分配了多次。如果内存泄漏经常发生或者十分巨大,最后你电脑的主要内存将被耗尽。程序首先会变慢,然后电脑开始使用分页技术虚拟内存,然后以内存溢出错误宣告失败。用普遍的调试器发现内存溢出错误是困难的因为它没有一个明显的错误声明。BUG会丢失或不能被声明。4.1.2 错误的内存管理用法一套完整BUG集合与错误声明对于内存管理来说:解除一个错误的内存超过一次,在解除以后访问内存,或解除一个从分配过的阻塞内存。用delete代替delete在C+数组中也属于这个类,也可以用malloc()和delete一起使用,用new和free()一起使用。4.1.3 缓存溢出缓存溢出内存被重载时超出范围的BUG,或损坏。缓存溢出会由属性引起,在堆中的局部变量,通过内存管理分配在栈上的动态变量。在内存中令人讨厌的事是内存重载的时候BUG没有被声明。只有在以后,另一个在问题中的声明将存取在内存中。因为内存含有一个不合法的变量,内存会在一下几个方面表现出异常:这个问题得出一个错误的结果,或者如果一个不合法的值在指针上,这个问题将试图存取和保护内存。如果在存取的过程中一个动态指针可变,程序将处理它按照程序代码。键指针不是引起内存溢出和严重问题的关键。4.1.4 非原始内存BUG读非原始内存是因为C或C+允许创建变量在没有一个原始值的情况下。程序员有完全的责任给所有的属性和局部变量赋值,也可以通过声明,或各种C+的构造器。内存引导功能malloc()和操作new也不能够定义变量或者是内存为空。未定义的变量中包含不稳定的值。4.2 内存调试解决上述问题处理BUG需要创建一个强大的BUG处理工具。寻找BUG与漏洞有关,不完善的,只有普通BUG调试器GDB内存变得不安全。应付大型软件工程中的系统漏洞,很多程序员都提出了相同的意见。他们建立内存管理机制为一个被分配阻塞内存利用不同的机制,并且确保是否每个阻塞内存在最后都得到了分配。自从每个人在各自的C或C+程序中遇到相同的内存BUG,自从每个人用传统的内存处理器追踪到很少的几个BUG,一系列叫内存调试器的工具诞生了。最总所周知的是Purify,在1991年被Pure软件公司发布。Purify一直被用于一些同类的内存调试器。也有Insure+,Valgrind,也有BoundsChecker,除此之外的一些其他同类工具。内存调试器作用于所有被动态分配的内存。他们同样拦截和检查分配过的内存。一些内存调试器能够检查一些被分配在栈上的局部变量和被分配过的内存。Purify和BoundsChecker用代码编译工具完成这些工作在程序连接期间,Insure+利用源代码编译工具,Valgrind则把程序先执行在虚拟机上并且监听所有的内存处理。源代码编译工具允许一个BUG发生的时候精确的找到代码发生的位置。一下的BUG被内存调试器处理:1. 内存泄漏2. 访问已经被释放的内存3. 释放一段内存空间不止一次4. 释放从未被分配过的内存5. 把C中的malloc(),free()与C+中的 new/delette混杂在一起6. 在数组中用delete代替delete7. 数组越界错误8. 访问从未被分配过的内存9. 未被定义过的内存读取10. 空指针读或写我们将在下一章节演示如何为你的程序附属内存调试器,内存调试器如何发现和报告错误。4.3 例1检测内存访问错误我们第一个例子是在动态内存中分配一列,存取最后一个在数组序列外的元素,读一个未定义的数组元素,最终忘记分配这个数组。我们用公共范围工具Valgrind在Linux上作为内存调试器,演示内存调试器如何自动的解决这些BUG。4.3.1 检查一个无用的写操作第一个最大最严重的错误是缓存溢出:对这个数组以外的写操作。因为这个数组只有100个元素,最大的有效索引是99。100指针超过了这个数组的有效范围。一次报告一个无用的写错误。=11323= Invalid write of size 4=11323= at 0x8048518: main (main1.c:9)=11323= Address 0x1BB261B8 is 0 bytes after a block=11323= of size 400 allocd=11323= at 0x1B903F40: malloc=11323= (in /usr/lib/valgrind/vgpreload_memcheck.so)=11323= by 0x80484F2: main (main1.c:6)这个字符串,应用一个过程ID,当Valgrind检查过程的时候是十分必要的。最重要的信息块是在第9行的写操作。这依然有信息补充说明根据以离内存最近的区域和它是怎么被分配的。内存调试器猜测第9行与内存阻塞有关。这个猜测被修正因为两个都属于数组A。在Valgrind中内存中数组下标越界错误只有当动态内存被定义为malloc()或new的情况下。这个是第6行的实例: 6 int*A = (int * )malloc (sizeof(int) * size)如果例子中书写Asize在第6行,A将被定义在一个局部变量上,堆而不是栈上。它表现出Valgrind不能够发现这样的错误但是Purity可以捕获到它们。它说明不是所有的内存调试器都能捕获到同一个错误。