C.4.3.  Finding Memory Errors

[ fromfile: findingmemoryerrors.xml id: findingmemoryerrors ]

Memory errors are difficult to track down without the aid of a runtime analysis tool. A program that analyzes the running performance of a program is called a profiler. valgrind is an open source profiling tool for Linux that tracks the memory and CPU usage of your code and detects a variety of runtime errors. These include

Any of these errors can cause catastrophic results in a piece of software. Profilers can also be used for performance-tuning, and determining which code is responsible for slowing down a program (i.e., finding bottlenecks).

Example C.5 is a short program that contains a deliberate memory usage error.

Example C.5. src/debugging/wrongdelete.cpp

void badpointer1(int* ip, int n) {
  ip = new int[n];
  delete ip;   1
}

int main() {
  int* iptr;
  int num(4);
  badpointer1(iptr, num); 
}

1

Wrong delete syntax.


For the output to be human readable, compile with debugging symbols (-g).

debugging/wrongdelete> g++ -g -Wall wrongdelete.cpp
debugging/wrongdelete> ./a.out
debugging/wrongdelete>

The compiler didn't complain, and even after running the program, no error behavior appears. However, memory is corrupted by this program.

Here is a (slightly abbreviated) look at valgrind's analysis of Example C.5. We have removed the process id of the valgrind job from the beginning of each line. The process id is, of course, different each time you run valgrind.

src/debugging> valgrind a.out
--3332-- DWARF2 CFI reader: unhandled CFI instruction 0:50
--3332-- DWARF2 CFI reader: unhandled CFI instruction 0:50
 Mismatched free() / delete / delete []
    at 0x401C1CB: operator delete(void*) (vg_replace_malloc.c:246)
    by 0x80484BD: badpointer1(int*, int) (wrongdelete.cpp:3)
    by 0x80484F4: main (wrongdelete.cpp:9)
  Address 0x4277028 is 0 bytes inside a block of size 16 alloc'd
    at 0x401BBF4: operator new[](unsigned) (vg_replace_malloc.c:197)
    by 0x80484AC: badpointer1(int*, int) (wrongdelete.cpp:2)
    by 0x80484F4: main (wrongdelete.cpp:9)

valgrind found the errors and, with debugging symbols, could point you to the location of the problem code. Example C.6 is a little more interesting because it contains memory leaks and array index errors.

Example C.6. src/debugging/valgrind-test.cpp

#include <iostream>

int badpointer2(int k) {
  int* ip = new int[3];
  ip[0] = k;
  return ip[3];                   1
}                                 2


int main() {
  using namespace std;
  int* iptr;                      3
  int num(4), k;                  4
  cout << iptr[num-1] << endl;    5
  cout << badpointer2(k) << endl; 6
}

1

Out of bounds index

2

Memory leak: allocated memory is no longer accessible.

3

iptr is uninitialized.

4

k is uninitialized.

5

What is the state of iptr?

6

Sending uninitialized arg to function.


Running Example C.6 through valgrind shows you the exact locations of some errors:

 For more details, rerun with: -v

--2164-- DWARF2 CFI reader: unhandled CFI instruction 0:50
--2164-- DWARF2 CFI reader: unhandled CFI instruction 0:50
 Use of uninitialised value of size 4
    at 0x80486AF: main (valgrind-test.cpp:17)
68500558

 Invalid read of size 4
    at 0x804867C: badpointer2(int) (valgrind-test.cpp:8)
    by 0x80486DD: main (valgrind-test.cpp:18)
  Address 0x4277034 is 0 bytes after a block of size 12 alloc'd
    at 0x401BBF4: operator new[](unsigned) (vg_replace_malloc.c:197)
    by 0x8048667: badpointer2(int) (valgrind-test.cpp:6)
    by 0x80486DD: main (valgrind-test.cpp:18)
0

 ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 19 from 1)
 malloc/free: in use at exit: 12 bytes in 1 blocks.
 malloc/free: 1 allocs, 0 frees, 12 bytes allocated.
 For counts of detected errors, rerun with: -v
 searching for pointers to 1 not-freed blocks.
 checked 120,048 bytes.

 LEAK SUMMARY:
    definitely lost: 12 bytes in 1 blocks.
      possibly lost: 0 bytes in 0 blocks.
    still reachable: 0 bytes in 0 blocks.
         suppressed: 0 bytes in 0 blocks.
 Use --leak-check=full to see details of leaked memory.

If this is not enough information to find where the memory leak is, you can rerun valgrind with --leak-check=full. We repaired some of the errors in Example C.7

Example C.7. src/debugging/valgrind-test2.cpp

#include <iostream>

int notSoBadPointer(int k) {
  int* ip = new int[3];
  ip[0] = k;  
  delete[] ip;                    1
  return k;                       2
}  


int main() {
  using namespace std;
  int* iptr;                      3
  int num(4), k(4);               4
  cout << iptr[num-1] << endl;    5
  cout << notSoBadPointer(k) << endl;
}

1

Clean up memory leak.

2

A returnable value.

3

Uninitialized pointer!

4

At least k is no longer uninitialized.

5

Here's trouble!


Compiling and running the slightly repaired test without valgrind produces no warnings or errors and one nonsense output value.

src/debugging> g++ -g -Wall valgrind-test2.cpp
src/debugging> ./a.out
-1078391036
4

Running it with valgrind produces fewer complaints than before.

src/debugging> valgrind ./a.out
 For more details, rerun with: -v

 Use of uninitialised value of size 4
    at 0x8048794: main (valgrind-test2.cpp:18)
-1096641724
4

 ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 18 from 1)
 malloc/free: in use at exit: 0 bytes in 0 blocks.
 malloc/free: 1 allocs, 1 frees, 12 bytes allocated.
 For counts of detected errors, rerun with: -v
 All heap blocks were freed -- no leaks are possible.
src/debugging>    

Finally, in Example C.8, we eliminate the last error.

Example C.8. src/debugging/valgrind-test3.cpp

#include <iostream>

int notSoBadPointer(int k) {
  int* ip = new int[3];
  ip[0] = k;  
  delete[] ip;                    1
  return k;                       2
}  


int main() {
  using namespace std;
  int num(4), k(4);               3
  int* iptr = new int[num] ;      4
  for (int i = 0; i < num; ++i)
     iptr[i] = i;
  cout << iptr[num-1] << endl;    5
  cout << notSoBadPointer(k) << endl;
  delete[] iptr;
}

1

Clean up memory leak.

2

A returnable value.

3

At least k is no longer uninitialized.

4

No longer uninitialized pointer.

5

No more trouble!


We compile, run, and then run with valgrind.

src/debugging> g++ -g -Wall valgrind-test3.cpp
src/debugging> ./a.out
3
4
src/debugging> valgrind ./a.out
3
4

 ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 18 from 1)
 malloc/free: in use at exit: 0 bytes in 0 blocks.
 malloc/free: 2 allocs, 2 frees, 28 bytes allocated.
 For counts of detected errors, rerun with: -v
 All heap blocks were freed -- no leaks are possible.
src/debugging>       

valgrind is not readily available for MacOSX[128] but the Mac Developer Tools include a graphical tool, called MallocDebug.app, that can replace a number of valgrind's functions.



[128] It can be installed from source code, which you can find at http://valgrind.org/downloads/repository.html.