Почему у меня возникают утечки памяти? Чего-то не хватает в следующем коде C?

Я пытаюсь написать собственную библиотеку в c для личного проекта. Библиотека включает в себя некоторые общие структуры данных. Я выделяю список в куче и думаю, что правильно его очищаю, но это не так. При запуске leaks --atExit --list -- ./main (mac m1) я получаю в общей сложности 4 утечки в следующем коде.

leaks Report Version: 3.0
Process 18747: 221 nodes malloced for 17 KB
Process 18747: 4 leaks for 96 total leaked bytes. 

Трассировка стека выглядит следующим образом:

Leak: 0x14e7040f0  size=16  zone: MallocStackLoggingLiteZone_0x102a40000   malloc in main  C  main
    Call stack: 0x180c6be50 (dyld) start | 0x1028475a8 (main) main  main.c:38 | 0x180dffd00 (libsystem_malloc.dylib) _malloc_zone_malloc_instrumented_or_legacy

Leak: 0x14e704100  size=16  zone: MallocStackLoggingLiteZone_0x102a40000   malloc in main  C  main
    Call stack: 0x180c6be50 (dyld) start | 0x1028475c4 (main) main  main.c:41 | 0x180dffd00 (libsystem_malloc.dylib) _malloc_zone_malloc_instrumented_or_legacy

Leak: 0x14e704180  size=32  zone: MallocStackLoggingLiteZone_0x102a40000   malloc in node_init  C  main
    Call stack: 0x180c6be50 (dyld) start | 0x10284761c (main) main  main.c:48 | 0x102847a30 (main) owl_sll_binsert  singly_linked_list.c:88 | 0x102847ab8 (main) node_init  singly_linked_list.c:60 | 0x180dffd00 (libsystem_malloc.dylib) _malloc_zone_malloc_instrumented_or_legacy

Leak: 0x14e7041a0  size=32  zone: MallocStackLoggingLiteZone_0x102a40000   malloc in node_init  C  main
    Call stack: 0x180c6be50 (dyld) start | 0x10284761c (main) main  main.c:48 | 0x102847a30 (main) owl_sll_binsert  singly_linked_list.c:88 | 0x102847ac4 (main) node_init  singly_linked_list.c:61 | 0x180dffd00 (libsystem_malloc.dylib) _malloc_zone_malloc_instrumented_or_legacy

single_linked_list.c

#include "singly_linked_list.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>

/* Opaque type */

typedef struct SinglyLinkedList
{
    owl_sll_node_t *head;
    owl_sll_node_t *tail;
    u_long length;
    size_t size;
    void (*elfree)(void *item);
} owl_sll_t;

//

owl_sll_t *owl_sll_init(size_t size, void (*elfree)(void *item))
{
    owl_sll_t *list = malloc(sizeof(owl_sll_t));
    list->head = NULL;
    list->tail = NULL;
    list->length = 0;
    list->size = size;
    list->elfree = elfree;
    return list;
}

void owl_sll_free(owl_sll_t *list)
{
    if (!list->length)
    {
        free(list);
        return;
    }

    owl_sll_node_t *cursor = list->head;

    while (cursor)
    {
        if (list->elfree)
            list->elfree(cursor->data);
        else
            free(cursor->data);

        free(cursor);
        cursor = cursor->next;
    }

    free(list);
}

static owl_sll_node_t *node_init(void *data, size_t size)
{
    static unsigned int id = 0;
    owl_sll_node_t *node = malloc(sizeof(owl_sll_node_t));
    node->data = malloc(size);

    // copy the content of the data
    for (int i = 0; i < size; i++)
        *(char *)(node->data + i) = *(char *)(data + i);

    node->next = NULL;
    node->id = id++;
    return node;
}

static void node_free(owl_sll_t *list, owl_sll_node_t *node)
{
    if (!node) return;
    // break any link if exist
    node->next = NULL;

    if (list->elfree)
        list->elfree(node->data);
    else
        free(node->data);

    free(node);
}

void owl_sll_binsert(owl_sll_t *list, void *data)
{
    owl_sll_node_t *node = node_init(data, list->size);
    if (!list->head)
    {
        list->head = node;
        list->tail = node;
    }
    else
    {
        list->tail->next = node;
        list->tail = node;
    }
    list->length++;
}

void *owl_sll_bremove(owl_sll_t *list)
{
    if (!list->length) return NULL;
    owl_sll_node_t *cursor = list->head;

    while (cursor->next && cursor->next->next)
    {
        cursor = cursor->next;
    }

    void *data = cursor->data;

    node_free(list, cursor->next);
    list->tail = cursor;
    list->tail->next = NULL;
    list->length--;

    if (list->length == 0)
    {
        list->head = NULL;
        list->tail = NULL;
    }

    return data;
}

void owl_sll_finsert(owl_sll_t *list, void *data)
{
    owl_sll_node_t *node = node_init(data, list->size);
    if (!list->head)
    {
        list->head = node;
        list->tail = node;
    }
    else
    {
        node->next = list->head;
        list->head = node;
    }
    list->length++;
}

void *owl_sll_fremove(owl_sll_t *list)
{
    if (!list->length) return NULL;

    void *data = list->head->data;
    owl_sll_node_t *node_to_free = list->head;

    list->head = list->head->next;
    node_free(list, node_to_free);
    list->length--;

    if (list->length == 0)
    {
        list->head = NULL;
        list->tail = NULL;
    }

    return data;
}

void owl_sll_print(owl_sll_t *list, void (*format)(void *data), char *connection_sym)
{
    if (!list->length)
    {
        printf("EMPTY\n");
        return;
    }

    owl_sll_node_t *cursor = list->head;
    char *conn_sym = connection_sym ? connection_sym : "->";

    while (cursor)
    {
        format(cursor->data);
        printf(" %s ", conn_sym);
        cursor = cursor->next;
    }

    printf("END\n");
}

// Getters

owl_sll_node_t *owl_sll_head(owl_sll_t *list)
{
    return list->head;
}

owl_sll_node_t *owl_sll_tail(owl_sll_t *list)
{
    return list->tail;
}

unsigned long int owl_sll_length(owl_sll_t *list)
{
    return list->length;
}

single_linked_list.h

#ifndef linked_list_h
#define linked_list_h

#include <stdlib.h>

typedef struct Node
{
    unsigned int id;
    void *data;
    struct Node *next;
} owl_sll_node_t;

typedef struct SinglyLinkedList owl_sll_t;

owl_sll_t *owl_sll_init(size_t size, void (*elfree)(void *item));
void owl_sll_free(owl_sll_t *list);
void owl_sll_binsert(owl_sll_t *list, void *data);
void *owl_sll_bremove(owl_sll_t *list);
void owl_sll_finsert(owl_sll_t *list, void *data);
void *owl_sll_fremove(owl_sll_t *list);
void owl_sll_print(owl_sll_t *list, void (*format)(void *data), char *connector_symbol);

// Getters

owl_sll_node_t *owl_sll_head(owl_sll_t *list);
owl_sll_node_t *owl_sll_tail(owl_sll_t *list);
unsigned long int owl_sll_length(owl_sll_t *list);

#endif /* linked_list_h */

main.c

#include "data_structure/singly_linked_list.h"
#include <stdio.h>

typedef struct a
{
    int id;
} a_t;

typedef struct b
{
    int id;
    a_t *a;
} b_t;

void elfree(void *item)
{
    b_t *b = item;
    free(b->a);
    free(b);
}

int main(int argc, const char *argv[])
{
    printf("OWL 🦉\n");
    int a = 1, b = 2;
    a_t *a1 = malloc(sizeof(a_t));
    a1->id = 1;

    a_t *a2 = malloc(sizeof(a_t));
    a2->id = 2;

    owl_sll_t *list = owl_sll_init(sizeof(b_t), elfree);
    owl_sll_binsert(list, &(b_t){.id = 1, .a=a1 });
    owl_sll_binsert(list, &(b_t){.id = 2, .a=a2 });

    owl_sll_free(list);
}

Пожалуйста, обновите свой вопрос с помощью программы вместо неполных фрагментов кода. Например, вы не предоставили нам файлы заголовков, поэтому мы не можем скомпилировать ваш код. Два 16 байта, вероятно, являются a1 и a2 в main, которые вы не освобождаете и дублируете в owl_sll_binsert()

Allan Wind 01.01.2023 11:38

В owl_sll_free() you free(cursor) следующая строка — cursor = cursor->next, что является неопределенным поведением. Вам нужна временная точка на элемент, который вы удаляете, и переместите курсор перед тем, как вы освободитесь.

Allan Wind 01.01.2023 11:49

Вы добавили еще один фрагмент. Вам будет проще помочь, если вы объедините эти фрагменты в один файл программы. Скомпилируйте его, чтобы убедиться, что он работает, и обновите сообщение об ошибке, чтобы оно соответствовало этому файлу. Затем сверните, чтобы нам не приходилось пробираться через код, который работает. минимальный воспроизводимый пример.

Allan Wind 01.01.2023 11:51

@AllanWind Да, вы правы. Два 16 байта были из a1 и a2, которые я забыл освободить. Проблема в двух других блоках по 32 байта. Я обновил фрагменты кода и также включил заголовочный файл.

RHGPT 01.01.2023 11:51

u_long не объявлен. Вместо этого используйте size_t, кстати.

Allan Wind 01.01.2023 11:53

@AllanWind Ваше предложение создать временный объект, прежде чем я освобожу курсор, устранило проблему. Теперь у меня нет никаких утечек. Спасибо за помощь. В следующий раз я попытаюсь создать MRE вместо публикации фрагментов.

RHGPT 01.01.2023 12:03
elfree определенный в main.c нигде не используется. Зачем тебе это? elfree указатель в структуре списка равен нулю. Вы звоните free по данным. Где распределяются данные?
n. m. 01.01.2023 12:06

@AllanWind u_long из sys/types.h. В types.h есть typedef для длинных типов.

RHGPT 01.01.2023 12:08

@н.м. elfree будет предоставленной пользователем функцией для освобождения любых глубоко вложенных структур. Во время инициализации списка пользователь может передать функцию elfree, если у него есть вложенная структура в данных узла, которую необходимо очистить. Цель состоит в том, чтобы предоставить гибкий способ освобождения данных. Если нет вложенных структур и данные могут быть освобождены нормально, elfree может быть NULL. В owl_sll_free в цикле я проверяю, предоставил ли пользователь пользовательскую функцию очистки (elfree) и, если предоставлено, вызывает ее вместо этого.

RHGPT 01.01.2023 12:13

Вам не нужно говорить мне это. Я понимаю, что он должен делать. Я не спрашиваю тебя об этом. Я вас спрашиваю, что он делает на самом деле, не в каком-то гипотетическом будущем пользовательском коде, а в той программе, которую вы нам показываете. Что делает elfree в main.c? (Ничего, нигде не используется). Что делает elfree внутри списка? (Ничего, это нулевой указатель). Так где же на самом деле выделен указатель, который фактически передается free? (Вы заполняете ответ).

n. m. 01.01.2023 12:25

@н.м. Извини, моя ошибка. Вы абсолютно правы, позвольте мне удалить код из фрагмента.

RHGPT 01.01.2023 12:28
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
11
79
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий
  1. В main() вам нужно освободить a1 и a2.

  2. В owl_sll_free() у вас есть использование после бесплатного неопределенного поведения:

        free(cursor);
        cursor = cursor->next;

вместо этого вам нужен указатель tmp на курсор, переместите курсор, а затем освободите, на что указатели tmp:

void owl_sll_free(owl_sll_t *list) {
    for(owl_sll_node_t *cursor = list->head; cursor;) {
        owl_sll_node_t *tmp = cursor;
        cursor = cursor->next;
        if (list->elfree)
            list->elfree(tmp->data);
        else
            free(tmp->data);
        free(tmp);
    }
    free(list);
}

У меня нет программы leaks, но valgrind доволен после этих двух изменений:

==553760== HEAP SUMMARY:
==553760==     in use at exit: 0 bytes in 0 blocks
==553760==   total heap usage: 8 allocs, 8 frees, 1,152 bytes allocated
==553760== 
==553760== All heap blocks were freed -- no leaks are possible

Бонусное предложение:

В owl_sll_init(), если вы откажетесь от бесплатного:

    list->elfree = elfree ? elfree : free;

Затем вы можете исключить условное выражение в own_sll_free():

void owl_sll_free(owl_sll_t *list) {
    for(owl_sll_node_t *cursor = list->head; cursor;) {
        owl_sll_node_t *tmp = cursor;
        cursor = cursor->next;
        list->elfree(tmp->data);
        free(tmp);
    }
    free(list);
}

Другие вопросы по теме