Почему функция list_destroy дважды освобождает current-> data , если это строка (поскольку двойное освобождение приводит к ошибке), но этого не происходит. list-> freeFn вызывает функцию, которая является свободным типом, преобразованным в char *, но затем free (current-> data) вызывается снова без преобразования типа, так что это не работает?

Почему мы должны набирать приведение для строк (char *), а не int, если бы это делалось для всех типов?

Оригинальный учебник: https://pseudomuto.com/ 2013 / 05 / реализация - а- родового - связанный список - в - с /

void list_destroy(list* list)
{
    listNode* current;
    while (list->head != NULL) {
        current = list->head;
        list->head = current->next;

        if (list->freeFn) {
            list->freeFn(current->data);
        }

        free(current->data);
        free(current);
    }
}

void free_string(void* data)
{
    free(*(char**)data);
}


void list_new(list* list, int elementSize, freeFunction freeFn)
{
    assert(elementSize > 0);
    list->logicalLength = 0;
    list->elementSize = elementSize;
    list->head = list->tail = NULL;
    list->freeFn = freeFn;
}

void main()
{
    ...
    list list;
    list_new(&list, sizeof(char*), free_string);
    ...
    list_destroy(&list);
}
0
userx1234 21 Янв 2020 в 19:00

3 ответа

Лучший ответ

Реализация общего списка хранит значения элементов фиксированного размера, предоставленные list_new(), вместе с указателем на необязательную функцию обратного вызова, чтобы освободить значение элемента, когда узел списка разрушен. Размер элемента сохраняется в члене elementSize списка, а указатель на функцию обратного вызова (то есть NULL, если не требуется вызывать функцию обратного вызова) сохраняется в члене freeFn.

list_append() и list_prepend() добавляют новый узел в список и снабжены указателем на значение элемента, которое будет добавлено в список. Они выделяют новый узел списка и выделяют блок памяти, на который указывает node->data, для хранения значения элемента, которое копируется в выделенный блок памяти с помощью memcpy().

list_destroy() освобождает все узлы в списке, вызывая функцию обратного вызова freeFn (если имеется) с указателем data для каждого значения элемента. Это зависит от функции обратного вызова, чтобы определить, что нужно сделать со значением элемента, чтобы освободить его. Функция обратного вызова не освобождает память, указанную указателем data. Он использует значение, на которое указывает указатель data. Именно list_destroy() освобождает блок памяти node->data, который был выделен list_append() или list_prepend().


В целочисленном примере сохраняемое значение - это просто значение типа int. Член elementSize списка: sizeof(int). Указатель на функцию обратного вызова freeFn - NULL, потому что ничего не нужно делать для освобождения int - это просто число. В примере вызывается list_append() с указателем на значение int, которое необходимо сохранить. list_append() выделяет блок размером sizeof(int), на который указывает node->data, и копирует в него значение int.


В строковом примере сохраняемое значение представляет собой char *, который указывает на динамически размещаемый строковый буфер, выделенный strdup(). Этот буфер должен быть освобожден при уничтожении узла списка. Следовательно, строковый пример предоставляет указатель на функцию free_string для освобождения строкового буфера, который был выделен strdup(). В примере вызывается list_append() с указателем на значение char *, которое необходимо сохранить. list_append() выделяет блок размером sizeof(char *), на который указывает node->data, и копирует в него значение char *. (Примечание: оно копирует само значение указателя char *, а не содержимое строки. Содержимое строки было скопировано strdup().)

Функция обратного вызова free_string() получает общий указатель void *data на копию значения, на которое было указано при вызове list_append(). Он указывает на значение char *, поэтому первое, что ему нужно сделать, - это преобразовать общий указатель void * в указатель char **. Это делается с помощью оператора приведения типа: (char **)data. Затем он разыменовывает char **, чтобы получить значение char *: *(char **)data. Это char * значение указывает на память, которая была выделена strdup() и которую необходимо освободить: free(*(char **)data);.

Если бы было проще увидеть, что делает free_string(), он мог бы определить некоторые локальные переменные для хранения значений промежуточного указателя, например:

void free_string(void *data)
{
    char **p = data;  /* convert pointer type (same as: char **p = (char **)data;) */
    char *strbuf = *p; /* dereference the pointer (same as char *strbuf = p[0];) */
    free(strbuf); /* free the buffer pointed to by the dereferenced pointer */
}

Как видите, free_string не освобождает буфер, на который указывает указатель data. Он просто освобождает буфер, на который указывает значение char *, на которое указывает указатель data.

1
Ian Abbott 21 Янв 2020 в 18:10

Я думаю, что вы можете увидеть это, если вы сделаете свой собственный вкладыш. Возьмите строку из free_string и поместите ее в list_destroy. То, что у вас есть, вы делаете бесплатно данные дважды, но данные уже были освобождены. Однажды можно изменить free_string на следующее (приведение не требуется):

void free_string(void **data)
{
    free(*data);
    *data = 0;
}

И назовите это с list->freeFn(&current->data);

0
JonBelanger 21 Янв 2020 в 17:55

Результатом freeFn(current->data) будет free(*(char **)data), который отличается от free(current->data); хотя сомнительно. Обратите внимание, что разница возникает из-за разыменования аргумента data (& current-> data).

Учебное пособие неверно, так как данные члена не распределены. Возможное исправление было бы изменить:

    if(list->freeFn) {
      list->freeFn(current->data);
    } 
    free(current->data);

Чтобы :

if(list->freeFn) {
  list->freeFn(current->data);
}
if (current->data) { 
   free(current->data);
}

И измените:

void free_string(void* data)
{
    free(*(char**)data);
}

Чтобы :

void free_string(void* data)
{
    free(*(char**)data);
    *(char **)data = 0;
}
-1
mevets 21 Янв 2020 в 16:21