views:

58

answers:

2

I'm writing a function in C that takes in a linked list and a predicate and returns an array containing all values of the linked list satisfying this condition. Here's the function:

void **get_all_that(list_t *l, int (*pred)(const void *)) {
    void **vals = NULL;
    int i = 0; // Number of matches found
    const size_t vps = sizeof(void *);
    node_t *n = l->first;
    while (n) {
        if (pred(n->value)) {
            vals = (void **)realloc(vals, i*vps); // (*)
            vals[i] = n->value;
            i++;
        }
        n = n->next;
    }
    if (vals != NULL) {
        vals = (void **)realloc(vals, i*vps);
        vals[i] = NULL; // NULL-terminate array
    }
    return vals;
}

I passed in a predicate that returns 1 always (i.e. get_all_that is basically to_array), and I'm getting an error at the starred line on the iteration where i=4. The error on the backtrace (which was automatically printed from a SIGABRT) is "*** glibc detected *** ~/list/test: realloc(): invalid next size: 0x0804c0e8 ***"

I opened up GDB telling it to break right before calling realloc when i=4. I then tried calling realloc(vals, i*vps) manually from GDB and got the error message: "Inconsistency detected by ld.so: dl-minimal.c: 138: realloc: Assertion `ptr == alloc_last_block' failed! "

Anyone know what's going on?

+1  A: 

Your realloc is allocating one too few elements. Try replacing i by i+1. You should also check for failure of realloc before replacing the pointer you passed to it, since otherwise you will get a memory leak (not to mention crash since you fail to check for NULL) on failure, and removing the unnecessary and ugly casts from the return value of realloc would be nice too.

R..
That did it. Good catch. Why aren't the casts to void** necessary?
Nick
@Nick: Because `void *` is implicitly converted to and from any other pointer type (in C; not in C++).
caf
The whole point of `void` pointers (which `malloc` and `realloc` return) is that they automatically convert to any (non-function-) pointer type. Why it's a bad idea (in terms of style, maintainability, and masking errors) is a topic you can do a search on.
R..
A: 

And your first realloc calls the length with 0, which is a free. So the advice to put i+1 is doubly important.

tristopia
That's not a problem in and of itself, because the next call would pass the pointer returned by the 0-size call to `realloc` into `realloc` again. Whether `realloc` with size 0 returns a `NULL` pointer or a valid unique pointer (the standard allows either), the program would work as expected.
R..
It should also be noted that the GNU `malloc` behavior of returning a unique non-null pointer on zero-size allocation made it harder to find the bug, and changed what would have been an easy-to-catch null-pointer-dereference segfault into a mildly-difficult-to-find memory corruption bug.
R..