COMS 3157 Advanced Programming

Valgrind Error Messages

Background

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.

Four Important Valgrind Error Messages

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).

1. Invalid read

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);
}

2. Invalid write

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);
}

3. Conditional jump depends on uninitialized values

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);
}

4. Segmentation Fault

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