Understanding Valgrind Output

Valgrind is a program that checks for both memory leaks and runtime errors. A memory leak occurs whenever you allocate memory using keywords like new or malloc, without subsequently deleting or freeing that memory before the program exits. Runtime errors, as their name implies, are errors that occur while your program is running. These errors may not cause your code to crash, but they can cause unpredictable results and should be resolved.

Memory Leaks

If your program has memory leaks, they will appear at the bottom of the Valgrind output, as follows:

LEAK SUMMARY:
definitely lost: 410 bytes in 8 blocks
indirectly lost: 68 bytes in 2 blocks
possibly lost: 0 bytes in 0 blocks
still reachable: 1,136 bytes in 2 blocks
suppressed: 0 bytes in 0 blocks

Ultimately, your goal is to get each of these categories down to 0 bytes in 0 blocks. When this happens, the Leak Summary output will change to look as follows:

All heap blocks were freed -- no leaks are possible

Runtime Errors

Valgrind outputs runtime errors together with your program's output. For example, consider the following Valgrind segment:

==13470== Memcheck, a memory error detector
==13470== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==13470== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==13470== Command: ./a.out
==13470==
This is some of my program output.
==13470== Invalid write of size 4
==13470==    at 0x10886A: function4() (valgrind.cpp:4)
==13470==    by 0x10887B: function3() (valgrind.cpp:7)
==13470==    by 0x108887: function2() (valgrind.cpp:10)
==13470==    by 0x108893: function1() (valgrind.cpp:13)
==13470==    by 0x1088B2: main (valgrind.cpp:17)
==13470==  Address 0x5b7e188 is 8 bytes inside an unallocated block of size 4,120,160 in arena "client"
==13470==
This is more of my program output.

In the middle of running my code, Valgrind discovered a runtime error, in this case, Invalid write of size 4. Notice that it gives us a "stack trace", in other words, a list of which functions were called to get to the point where this error occurred. In this case, main() called function1(), which called function2(), which called function3(), which called function4(). function4() is where the error occurred. Notice that Valgrind even gives a line number: our error occurred in the file valgrind.cpp on line 4.

Sometimes, Valgrind will report that an error occurred in a function that you didn't even write. For example:

==2473== Process terminating with default action of signal 11 (SIGSEGV)
==2473== at 0x54ED152: write (write.c:27)
==2473== by 0x4EEFBB5: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25)
==2473== by 0x4F2C6E7: std::basic_filebuf >::overflow(int) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25)
==2473== by 0x4F2A468: std::basic_filebuf >::sync() (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25)
==2473== by 0x4F4EF5D: std::ostream::flush() (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25)
==2473== by 0x10B537: main (valgrind.cpp:180)

As you can see in the stack trace, an error occurred in the file write.c on line 27. But you didn't even write that! When this happens, the best thing to do is to follow the stack trace backwards until you find a function that you did write. In this example, we would follow the stack trace back until we found the main() function, specifically line 180 in valgrind.cpp.

Let's now consider the most frequent Valgrind errors that you might face in this class:

Conditional jump or move depends on uninitialized value(s)

This error message means that you have a loop or if statement that is dependent on an uninitialized variable (a variable whose value you haven't set). In the example below, notice that the integer x is only set if userInput is equal to true. But then, no matter what, we use x in the for-loop condition, even though it might not be set. This creates problems, because if you don't set the value of the variable, it will be set to some unknown value.

int main()
{
	int x;
	if (userInput == true)
	{
		x = 5;
	}
	for (int y = 0; y < x; y++)
	{
		std::cout << y << std::endl;
	}
}

Invalid write of size n

This error message most often occurs if you have written outside the bounds of memory you have allocated. For example, suppose you allocate an array of integers with 5 items, but then try to access the 8th element:

int main()
{
	int* arr = new int[5];
	arr[8] = 100;
	delete[] arr;
}

Mismatched (or Invalid) free() / delete / delete[] / realloc()

This error message can have several causes. The most common reason is forgetting to use delete[] with dynamically allocated arrays. In this example, the valgrind error could be corrected by simply adding the square brackets after delete.

int main()
{
	int* arr = new int[5];
	arr[2] = 100;
	delete arr;
}	

This can also occur if you don't correctly delete the memory you've allocated. Anytime you use new, you must delete with delete. Anytime you use malloc, you must delete using free. Attempting to mix and match, as seen below, will cause a Valgrind error:

int main()
{
	int* myInt = (int*)malloc(sizeof(int));
	*myInt = 5;
	delete myInt;
}

One other thing that can cause this problem is deleting the same thing twice:

int main()
{
	int* myInt = new int;
	delete myInt;
	delete myInt;
}	

Stack overflow in thread

In labs that use recursion, such as BST, you may run into this error. Most frequently this occurs when you don't have exhaustive base cases for a recursive function. Without all the necessary base cases, your recursive function will continually call itself until there is no more memory on the stack to call it.

Process terminating with default action of signal 11 (SIGSEGV)

This means that your program crashed with a Segmentation Fault. A segmentation fault generally occurs if you try to access memory that is already reserved by the system. In this class, this most commonly occurs if you try to access a data member of a NULL pointer, like this:

struct Node
{
	int x;
};

int main()
{
	Node* n = NULL;
	n->x = 5;
}

Process terminating with default action of signal 15 (SIGTERM)

In the autograder output, you will almost always see this error when your program's execution timed out (in this class, the time limit is 5 seconds). Like the stack overflow issue, this can happen with recursive functions that are missing necessary base cases. If this is the case, your Valgrind output will look something like this:

Process terminating with default action of signal 15 (SIGTERM)
==15932==    by 0x108818: function1() (valgrind.cpp:14)
==15932==    by 0x10881D: function1() (valgrind.cpp:15)
==15932==    by 0x10881D: function1() (valgrind.cpp:15)
==15932==    by 0x10881D: function1() (valgrind.cpp:15)
==15932==    by 0x10881D: function1() (valgrind.cpp:15)
==15932==    by 0x10881D: function1() (valgrind.cpp:15)
==15932==    by 0x10881D: function1() (valgrind.cpp:15)
==15932==    by 0x10881D: function1() (valgrind.cpp:15)
==15932==    by 0x10881D: function1() (valgrind.cpp:15)
==15932==    by 0x10881D: function1() (valgrind.cpp:15)

Notice that function1() continually called itself over and over until we ran out of time.