.hack//tk

.hack//tk

yet another technology blog

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>

int** allocateMatrix(int l, int c) {
int** m = (int**) malloc(l * sizeof(int*));

for (int i = 0; i < l; i++)
m[i] = (int*) malloc(c * sizeof(int));

return m;
}

void freeMatrix(int** m) {
free(m);
}

int main (void) {
int** m = allocateMatrix(2,3);
freeMatrix(m);

return 0;
}

Now we are going to use GCC to compile and execute our program.

1
2
gcc -o memory-leak memory-leak.c
./memory-leak

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.

Valgrind Memory Leak

The code below fixes the function freeMatrix(), freeing the line-vectors first and then the vector of pointers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>

int** allocateMatrix(int l, int c) {
int** m = (int**) malloc(l * sizeof(int*));

for (int i = 0; i < l; i++)
m[i] = (int*) malloc(c * sizeof(int));

return m;
}

void freeMatrix(int** m, int l) {
for (int i = 0; i < l; i++)
free(m[i]);
free(m);
}

int main (void) {
int** m = allocateMatrix(2,3);
freeMatrix(m, 2);

return 0;
}

Now in the Valgrind report, we can see that all memory blocks were allocated and properly freed.

1
2
gcc -o memory-leak-fixed memory-leak-fixed.c
valgrind ./memory-leak-fixed

Valgrind Memory Leak Fixed

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>

#define ELEMENTS 5

int* allocateVector(int n) {
int* v = (int*)malloc(n * sizeof(int));

for (int i = 0; i <= n; i++)
v[i] = i;

return v;
}


int main (void) {
int *v = allocateVector(ELEMENTS);

printf("The last vector element is: %d\n" , v[ELEMENTS]);

free(v);
}

As we can see below, the program was compiled and executed without any error.

Invalid Read and Write GCC

Now let’s execute the program using Valgrind to see if there is something wrong with this program.

Invalid Read and Write Valgrind

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.

Invalid Read and Write Valgrind Write

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
2
3
4
5
6
7
8
int* allocateVector(int n) {
int* v = (int*)malloc(n * sizeof(int));

for (int i = 0; i <= n; i++)
v[i] = i;

return v;
}

The second is a read error on a memory block, that occurs in the main() function, when calling the printf() function.

Invalid Read and Write Valgrind Read

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
2
3
4
5
6
7
int main (void) {
int *v = allocateVector(ELEMENTS);

printf("The last vector element is: %d\n" , v[ELEMENTS]);

free(v);
}

The code below fixes the code, reading and writing only from previously allocated memory blocks.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>

#define ELEMENTS 5

int* allocateVector(int n) {
int* v = (int*)malloc(n * sizeof(int));

for (int i = 0; i < n; i++)
v[i] = i;

return v;
}


int main (void) {
int *v = allocateVector(ELEMENTS);

printf("The last vector element is: %d\n" , v[ELEMENTS - 1]);

free(v);
}

Now in the Valgrind report, we can see that all reads and writes are done in reserved memory blocks.

1
2
gcc -o invalid-read-write-fixed invalid-read-write-fixed.c
valgrind ./invalid-read-write-fixed

Valgrind Invalid Read and Write Fixed

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.