Valgrind, the memory management analyser

In the program consisting of a few lines of code, you can find errors with memory management just by looking at the code. When the size of the program grows, finding errors becomes more difficult.

You can trace the memory management errors with a program called valgrind that must be executed separately. It analyzes the code and prints an error message if, for example,

  • your program allocates dynamic memory with new but does not deallocate it with delete
  • your program uses a variable (allocated dynamically or automatically) without a value set to it
  • you try to use dynamically allocated memory that has been deallocated already.

The tool valgrind is installed on linux-desktop. If you want, you also can install it on your own computer. You can execute valgrind in either Qt Creator or via the command line. For the sake of practice, we will in the next exercise, execute valgrind with both ways, so that we learn more than one way of using it.

Valgrind’s error messages

Some of the error messages have numbers 1, 4, 8. These numbers mean the size of a memory zone in bytes, required by a type. A character (char) requires only one byte, an integer (int) requires four bytes, and a pointer requires eight bytes.

In its column Location, valgrind tells a file name and a line number. That point is the one, where the error was detected. However, the erroneous point to be fixed can locate elsewhere (at some point that was executed before the detection point).

We will next introduce some of the most general error messages. Let’s assume that we have a program like the task list example (examples/09/task_list/) with the struct List_item.

Use of unitialized value of size 8

If memory is not allocated for an element of a linked list (with new), but you just write, for example, as:

List_item* new_item;

already the editor of Qt informs about an unitialized variable. If you still execute valgrind, you will get the error message in the title above (and perhaps also the error message in the next title).

If you just assign nullptr as:

List_item* new_item = nullptr;

and assign no values in the struct fields, valgrind informs nothing, but the program does work correctly.

If you, besides the above assignment, try to use the element new_item as

List_item* new_item = nullptr;
new_item->task = ...;

the program does nothing, and valgrind informs nothing.

Conditional jump or move depends on uninitialised value(s)

A field of a struct or another variable is left uninitialized. For example, in a linked list, a field of the struct has been given no value. If an unitialized field is not the next field, the error may occur when trying to print the value of the field. If you assign no value (not even nullptr) in the next field, the program may seem to work correctly. Alternatively, this may cause an infinite loop, when you go through the list as long as the value of the next field is not nullptr.

Invalid read of size N

It is tried to use (read) a memory cell already deallocated. For example:

delete item_to_be_removed;
...
cout << item_to_be_removed->task << endl;

Here the task field is a string, and thus, there is 8 in the place of N. If the field to be printed was of type int, N would be 4.

Invalid free() / delete / delete[]

It is tried to deallocate a memory cell already deallocated. For example:

delete item_to_be_removed;
...
delete item_to_be_removed;

The program may still work correctly, and thus, the error is hard to detect without valgrind.

If you, between the above lines, assign nullptr as:

delete item_to_be_removed;
item_to_be_removed = nullptr;
...
delete item_to_be_removed;

valgrind informs nothing, and program seems to work correctly.

N (…) bytes in M blocks are definitely lost in loss record X of Y

Memory cells were not deallocated, i.e. the command delete was forgotten.

The program may still work correctly, and thus, the error is hard to detect without valgrind.