When we run Valgrind, two types of information are outputted: memory leaks and memory errors. They are two distinct concepts. Memory leaks occur when we fail to free heap memories after mallocing. The rest of this guide focuses on memory errors.
There are four error messages you should understand: 1) invalid read, 2) invalid write, 3) conditional jump depends on uninitialized value(s), and 4) segmentation fault (sometimes called segfault).
An invalid read occurs when you attempt to read a value from a location in memory outside of what your program has allocated, either on the stack or in the heap from malloc
. The error message always includes the number of bytes your program attempted to dereference, which is useful for debugging the error. A size of 1 indicates a char
, a size of 4 indicates an int
, and a size of 8 often indicates a pointer.
Here’s a simple program that will lead to an invalid read of size 4 by reading 4 bytes (one extra int
) beyond a heap-allocated array of integers. The program allocates 20 bytes, but attempts to dereference and read values from 24.
int main() {
int *p = malloc(5 * sizeof(int));
if (p == NULL) {
exit(1);
}
for (int i=0; i<5; i++) {
p[i] = i;
}
for (int i=0; i<=5; i++) {
printf("%d\n", p[i]);
}
free(p);
}
An invalid write is very similar to an invalid read, but it occurs when you attempt to write a value to a location in memory outside of what your program has allocated.
Here’s a simple program that causes an invalid write of size 4 by writing one extra int
outside of a heap-allocated array. This program allocates 20 bytes (5 ints
), but attempts to write values to 24 bytes by writing 6 ints
.
int main() {
int *p = malloc(5 * sizeof(int));
if (p == NULL) {
exit(1);
}
for (int i=0; i<=5; i++) {
p[i] = i;
}
for (int i=0; i<5; i++) {
printf("%d\n", p[i]);
}
free(p);
}
The error message “conditional jump or move depends on uninitialized value(s)” indicates that the outcome of a conditional statement like an if
-statement or while
-loop in your program depends on the value of a variable that has not been initialized.
Because the value of an uninitialized variable cannot necessarily be predicted, the behavior of your program is also undetermined.
This occurs frequently if an uninitialized value occurs in an if
statement in your own code, but can also occur if you pass an uninitialized value into some other library function. One case of this is printf
, which will lead to a series of conditional jump errors if passed an uninitialized value.
Here’s a sample program that raises a few conditional jump errors. The first is a result of passing an uninitialized value into an if
statement, and the rest are raised by passing that value into printf
.
int main() {
int x;
if (x < 500) {
printf("%d is less than 500!\n", x);
} else {
printf("%d is not less than 500!\n", x);
}
}
This error can also occur when passing a non-null-terminated string to printf
. Here’s another sample program, which attempts to print a char
array. This char
array, instead of being null-terminated, is followed by an uninitialized value.
int main() {
char *s = "hello";
char p[6];
for(int i=0; i<5; i++) {
p[i] = s[i];
}
printf("%s\n", p);
}
A segmentation fault occurs when you try to dereference an invalid memory location, or write to a read-only location in memory.
The simplest example of this is attempting to dereference a null pointer, but segmentation faults can often occur as a result of the previously mentioned memory errors. Because of this, if your program crashes with a segmentation fault, your first step should be investigating using Valgrind.
Here’s a small sample program that tends to lead to a segmentation fault. It declares an int *p
but does not initialize it, meaning that p
can be pointing to any location in memory. The next line writes to that undetermined location in memory, which can easily lead to a segmentation fault.
int main() {
int *p1;
*p1 = 5;
}
Here’s another program, which attempts to write to read-only memory. The char *s
points to the read-only code section of memory, but the char
arrays s1
and s2
are on the stack, which can be read from and written to. The first call to strcpy
attempts to read from s
and write to s1
, which is allowed. The second call attempts to read from s2
and write to s
, which is not allowed, and leads to a segmentation fault.
int main() {
char *s = "hi";
char s1[3] = "no";
char s2[3] = "ok";
printf("%s %s %s\n", s, s1, s2);
strcpy(s1,s); // this is okay
printf("%s %s %s\n", s, s1, s2);
strcpy(s,s2); // this is not
printf("%s %s %s \n", s, s1, s2);
}
This guide was originally written by Brennan, a former AP TA