Ошибка C Realloc - утверждение `ptr == alloc_last_block' не удалось!

Я пишу функцию на C, которая принимает связанный список и предикат и возвращает массив, содержащий все значения связанного списка, удовлетворяющие этому условию. Вот функция:

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

Я передал предикат, который всегда возвращает 1 (т.е. get_all_that в основном to_array), и я получаю сообщение об ошибке в отмеченной звездочкой строке на итерации, где i=4. Ошибка в трассировке (которая была автоматически напечатана из SIGABRT): «*** glibc обнаружен *** ~/list/test: realloc(): неверный следующий размер: 0x0804c0e8 ***»

Я открыл GDB, сказав, что он должен сломаться прямо перед вызовом realloc, когда i=4. Затем я попытался вручную вызвать realloc(vals, i*vps) из GDB и получил сообщение об ошибке: «Несоответствие, обнаруженное ld.so: dl-minimal.c: 138: realloc: Assertion `ptr == alloc_last_block' failed!»

Кто-нибудь знает, что происходит?


person Nick    schedule 05.10.2010    source источник


Ответы (2)


Ваш realloc выделяет слишком мало элементов. Попробуйте заменить i на i+1. Вы также должны проверить наличие ошибки realloc перед заменой переданного вами указателя, так как в противном случае вы получите утечку памяти (не говоря уже о сбое, поскольку вы не проверите NULL) при ошибке и удалите ненужные и уродливые приведения из возвращаемое значение realloc тоже было бы неплохо.

person R.. GitHub STOP HELPING ICE    schedule 05.10.2010
comment
Это сделало это. Хороший улов. Почему не нужны приведения к void**? - person Nick; 05.10.2010
comment
@Nick: Потому что void * неявно преобразуется в любой другой тип указателя и из него (в C, а не в C++). - person caf; 05.10.2010
comment
Весь смысл указателей void (которые возвращают malloc и realloc) заключается в том, что они автоматически преобразуются в любой (нефункциональный) тип указателя. Почему это плохая идея (с точки зрения стиля, ремонтопригодности и маскировки ошибок) — это тема, по которой вы можете выполнить поиск. - person R.. GitHub STOP HELPING ICE; 05.10.2010

И ваш первый realloc вызывает длину с 0, что является free. Так что совет поставить i+1 важен вдвойне.

person Patrick Schlüter    schedule 05.10.2010
comment
Это не проблема само по себе, потому что следующий вызов передаст указатель, возвращенный вызовом размера 0 к realloc, снова в realloc. Независимо от того, возвращает ли realloc с размером 0 указатель NULL или действительный уникальный указатель (стандарт допускает и то, и другое), программа будет работать так, как ожидалось. - person R.. GitHub STOP HELPING ICE; 05.10.2010
comment
Следует также отметить, что поведение GNU malloc по возврату уникального ненулевого указателя при выделении памяти нулевого размера усложнило поиск ошибки и изменило то, что было бы легко отловимой ошибкой сегментации при разыменовании нулевого указателя, на довольно трудно найти ошибку повреждения памяти. - person R.. GitHub STOP HELPING ICE; 05.10.2010