Optimizing your code using Valgrind
One of the most common and difficult errors to detect and resolve in C programs are bad memory management. Errors such as, allocate memory blocks and forget to deallocate after usage, try to read or write in a memory block that was not previously reserved, do not initialize control variables or pointers correctly.
There are many tools to help you find these errors. One of them is Valgrind, that provides a series of debug tools and analyze your program. The most popular tool is the Memcheck, that can detect memory errors.
In this tutorial, we are going to create two of the most common bad memory management situations that are easily caught by using Valgrind, check Valgrind’s report and fix them properly.
Memory Leak
Memory leak happens when memory blocks are allocated by a program to accomplish a certain task but are not freed after its usage. The following program shows allocs and frees memory for a matrix.
1 |
|
Now we are going to use GCC to compile and execute our program.
1 | gcc -o memory-leak memory-leak.c |
Note that after we compile and execute our program, no error message was displayed. However, if we analyze the function that frees the memory freeMatrix(), we are going to be able to detect a problem: only the allocation for the vector of pointers is being freed, the line-vectors allocated are not freed. Now we are going to run Valgrind, and this problem will be pointed out.
1 | valgrind ./memory-leak |
In the Valgrind report, we can see that 3 memory blocks were allocated but only one was freed. To get more information about the memory leak, we can run Valgrind with the -leak-check=full flag.
The code below fixes the function freeMatrix(), freeing the line-vectors first and then the vector of pointers.
1 |
|
Now in the Valgrind report, we can see that all memory blocks were allocated and properly freed.
1 | gcc -o memory-leak-fixed memory-leak-fixed.c |
Invalid read and write
Another very common mistake is trying to read or write from memory blocks that were not reserved for the program. The following source code shows this.
1 |
|
As we can see below, the program was compiled and executed without any error.
Now let’s execute the program using Valgrind to see if there is something wrong with this program.
As we can see, the Valgrind report is pointing out two errors.
The first one is a write error on a memory block, that occurs in the allocateVector() function, called by the main() function.
Analyzing the code, we can see that, in the allocateVector() function, a memory block was allocated for n integers (n = 5). The for loop used to fill the vector, goes from the position 0 to n, trying to write in a memory area that was not previously reserved, since the last valid position of the vector is 4.
1 | int* allocateVector(int n) { |
The second is a read error on a memory block, that occurs in the main() function, when calling the printf() function.
Analyzing the code, we can see that, the printf() function receives an argument (v[MAX] = v[5]), that reads a memory block that was not reserved by the program since the last valid position of the vector is 4.
1 | int main (void) { |
The code below fixes the code, reading and writing only from previously allocated memory blocks.
1 |
|
Now in the Valgrind report, we can see that all reads and writes are done in reserved memory blocks.
1 | gcc -o invalid-read-write-fixed invalid-read-write-fixed.c |
Ready!
This concludes our tutorial on how to detect and resolve bad memory management errors in C language. These are not the only errors that Valgrind is able to detect, if you are interested in this topic, you can continue your research it reading Valgrind’s Documentation.