Recitation 11: Malgrind

You can find the code and solutions for this recitation on CLAC, here:

$ git clone ~j-hui/cs3157-pub/examples/malgrind

Consider malgrind.c, which defines MALLOC() and FREE():

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

static uint64_t MAGIC = ~((uint64_t) 0);

const size_t WAD = sizeof(MAGIC);

void *MALLOC(size_t n) {
    char *p = malloc(2 * WAD + n);
    if (p == NULL)
        return NULL;

    uint32_t *head = (uint32_t *)p;
    head[0] = head[1] = n;
    memcpy(p + WAD + n, &MAGIC, WAD);
    return p + WAD;
}

void FREE(void *p) {
    if (p == NULL) {
        fprintf(stderr, "%s\n", "NULL");
        return;
    }

    uint32_t *head = (uint32_t *)p - 2;
    if (head[0] != head[1] || memcmp((char *)p + head[0], &MAGIC, WAD) != 0)
        fprintf(stderr, "%s\n", "OOPS");
    else
        fprintf(stderr, "%s\n", "OKAY");

    free((char *)p - WAD);
}

MALLOC() and FREE() provide the same interface as the underlying malloc() and free() functions, in that they allocate and release heap memory. Which following describe the potential benefits of using MALLOC() and FREE() instead of the malloc() and free() functions they wrap?

  • A. Using MALLOC() and FREE() can make the program run faster.
  • B. Using MALLOC() and FREE() can make the program use less memory.
  • C. Using MALLOC() and FREE() can help detect memory leaks.
  • D. Using MALLOC() and FREE() can help detect invalid writes.
  • E. Using MALLOC() and FREE() can help detect invalid reads.
  • F. Using MALLOC() and FREE() can help detect invalid free()s.

Select zero or more of the above. You do not have to answer this right now; work on some of the problems below before returning to these questions.

11.1 mgtest1

Here is a test program, mgtest1.c, which uses the MALLOC() and FREE() functions:

#include <stdio.h>

void *MALLOC(size_t n);
void FREE(void *p);

int main(void) {
    int *a = MALLOC(4 * sizeof(int));

    for (int i = 0; i < 5; i++)
        a[i] = i;

    for (int i = 0; i < 5; i++)
        fprintf(stderr, "%d, ", a[i]);

    FREE(a);

    return 0;
}

We compiled and linked the mgtest1 program, and ran it with and without Valgrind:

$ gcc -g -Wall mgtest1.c malgrind.c -o mgtest1

$ valgrind --leak-check=yes ./mgtest1           # with Valgrind
(( output not shown ))

$ ./mgtest1                                     # without Valgrind
(( output not shown ))

What is the outcome of running mgtest1 with Valgrind? Choose from one of the following:

  • ERROR: Valgrind detects memory errors.
  • LEAK: Valgrind reports memory leaks, but no other memory errors.
  • GOOD: Valgrind reports no memory leaks or errors.

What is the output of the program, when run without Valgrind?

  • Assume that the program doesn’t crash due to invalid reads and writes.

  • The output of the program contains a comma-separated sequence of integers, read from memory; if you think an integer is read from an invalid or uninitialized memory location, write X instead of a decimal value.

  • The sequence of integers is terminated by OKAY, OOPS, or NULL, printed when calling FREE(). Make sure to include this your output.

11.2 mgtest2

We replace mgtest1.c with a modified version of the test, mgtest2.c:

#include <stdio.h>
#include <assert.h>

void *MALLOC(size_t n);
void FREE(void *p);

int main(void) {
    assert(sizeof(long) == sizeof(void *));

    long *a = MALLOC(4 * sizeof(long));

    for (long i = 0; i < 4; i++)
        a[i] = i;

    for (long *p = a; *p >= 0; p++)
        fprintf(stderr, "%ld, ", p[1]);

    FREE((void *) *a);

    return 0;
}

Like before, we compiled and linked the mgtest2 program, and ran it with and without Valgrind:

$ gcc -g -Wall mgtest2.c malgrind.c -o mgtest2

$ valgrind --leak-check=yes ./mgtest2           # with Valgrind
(( output not shown ))

$ ./mgtest2                                     # without Valgrind
(( output not shown ))

What is the outcome of running mgtest2 with Valgrind? (ERROR/LEAK/GOOD)

What is the output of the mgtest2 program, when run without Valgrind? (Comma-separated decimal integers + word, same rules as before.)

11.3 mgtest3

Here’s another variation of the test program, mgtest3.c:

#include <stdio.h>
#include <assert.h>

void *MALLOC(size_t n);
void FREE(void *p);

int main(void) {
    assert(sizeof(int) == 4);

    int *a = MALLOC(4 * sizeof(int));

    for (int i = 0; i < 4; i++)
        a[i] = i;

    for (int *p = a - 2; p < a + 6; p++)
        fprintf(stderr, "%d, ", *p);

    FREE(a);

    return 0;
}

Like before, we compiled and linked the mgtest3 program, and ran it with and without Valgrind:

$ gcc -g -Wall mgtest3.c malgrind.c -o mgtest3

$ valgrind --leak-check=yes ./mgtest3           # with Valgrind
(( output not shown ))

$ ./mgtest3                                     # without Valgrind
(( output not shown ))

What is the outcome of running mgtest3 with Valgrind? (ERROR/LEAK/GOOD)

What is the output of the mgtest3 program, when run without Valgrind? (Comma-separated decimal integers + word, same rules as before.)