Skip to main content

Section 22.6 Detecting Leaks and Use-After-Free Errors

As mentioned in the previous section a memory leak does not do any immediate harm. Because of this, it can be difficult to detect and diagnose memory leaks in a program. However, there are several techniques and tools that can help us identify memory leaks.
Some compilers include built in tools that can be used to add code that monitors allocations and deallocation and detects memory leaks. One such tool, AddressSanitizer, is available in GCC (the compiler used by this book) and can be enabled with the -fsanitize=address flag. It is most useful if you also use the -g flag to include debug information in the compiled binary.

Note 22.6.1.

AddressSanitizer does not work on GCC for Windows. But it does work when using GCC on Windows Subsystem for Linux (WSL).
The following program demonstrates what AddressSanitizer does. It is setup to compile with something like:
$ g++ -fsanitize=address -g detect-leaks.cpp -o program.exe
Try running the program. You will see the program output and then a memory report from AddressSanitizer.
Listing 22.6.1.
The report should indicate two leaks. One for the integer allocated with new int(5) and another for the string allocated with new string("Hello, world"). Add the line delete p1; at the end of foo, and run the program again. Now you should only get one memory leak (the one for the string).
Each leak report looks like:
Direct leak of 32 byte(s) in 1 object(s) allocated from:
    #0 0x7f058b6bb548 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:95
    #1 0x55792de515bd in foo() /home/jobe/runs/jobe_9E1yY2/test.cpp:10
    #2 0x55792de51805 in main /home/jobe/runs/jobe_9E1yY2/test.cpp:19
    #3 0x7f058b0401c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 42c84c92e6f98126b3e2230ebfdead22c235b667)
    #4 0x7f058b04028a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 42c84c92e6f98126b3e2230ebfdead22c235b667)
    #5 0x55792de51404 in _start (/home/jobe/runs/jobe_9E1yY2/test.cpp.exe+0x2404) (BuildId: 2d2506729042a70f415e3a189abe98bf4c37c123)
Here is how to read that:
  • The first line indicates the type and size of the leak (e.g., "Direct leak of 32 byte(s) in 1 object(s) allocated from:").
  • Each subsequent line shows a stack trace of the allocation, with the most recent call at the top. The format of each line is: #n ADDRESS in FUNCTION (FILE:LINE).
    1. The ADDRESS is the memory address where the allocation occurred.
    2. The FUNCTION, FILE, and LINE number indicate where in the code the allocation was made.
So, for this leak, we can see:
  • Header.
    The leak is 32 bytes. That must be how much memory was allocated for the string. (The other leak was 4 bytes because an integer is always 4 bytes.)
  • #0.
    It was allocated by new from the asan library. We probably don’t care about that. The library did not cause our leak.
  • #1.
    new was called from foo() on line 10 of test.cpp. (Don’t worry about /home/jobe/runs/jobe_5QqFtF/, that is just the folder on the server the code was in when it ran.) This is the important piece of information. It is the first code that we wrote that is responsible for the leak.
  • #2.
    foo() was called from main on line 19 of test.cpp. This is also important because it shows us the chain of function calls that led to the leak. If foo() was called multiple times, this would help us figure out which call was responsible for the leak.
  • #3-5.
    These are the C++ library calls that setup the run of the program and called our main function. We don’t care about any of those.
Once you know where leaked memory was allocated, you can try to decide where to delete that memory. Often times, you will not be able delete the memory where it was allocated. Instead, you will have to trace the use of that memory through the code and decide when the program is done with the memory. That is the point to delete it.

Insight 22.6.2.

A memory leak report tells you there is an issue. It does not tell you where to fix that issue.
AddressSanitizer will also detect some other common mistakes. The even more dangerous counterpart to a leak is a use-after-free error. That is when after deleting some memory you try to use it. Add a use-after-free error to the end of foo() by making it look like:
Listing 22.6.2.
      ...
      cout << "p2 points to: " << *p2 << endl;
      delete p1;
      cout << *p1;  // Use after free error
}
...
If you run the program again, you should see a use-after-free error reported by AddressSanitizer. There is a lot of other information it provides, but the key bit is in the first two lines of the report. They should say something like:
==55472==ERROR: AddressSanitizer: heap-use-after-free on address 0x502000000010 at pc 0x55817502f795 bp 0x7ffd453d5840 sp 0x7ffd453d5830
READ of size 4 at 0x502000000010 thread T0
    #0 0x55817502f794 in foo() /home/jobe/runs/jobe_nZISUA/test.cpp:16
...
That tells us we used address 0x502000000010 after freeing it. We used it on line 16 of test.cpp. Looking down further, we can see a report about when it was freed:
...
0x502000000010 is located 0 bytes inside of 4-byte region [0x502000000010,0x502000000014)
freed by thread T0 here:
    #0 0x7ff94b2635e8 in operator delete(void*, unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:164
    #1 0x55817502f75a in foo() /home/jobe/runs/jobe_nZISUA/test.cpp:15
...
It was freed by delete, which was called from foo() on line 15 of test.cpp.

Note 22.6.3.

Programs in this book where you are expected to work with dynamic memory will be configured to use address sanitizer to help detect memory issues. When building your own programs that involve memory management, you should use AddressSanitizer or a similar tool to check for leaks and use-after-free errors.

Checkpoint 22.6.1.

You have attempted of activities on this page.