Я пытаюсь написать собственную библиотеку в 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);
}
В owl_sll_free()
you free(cursor)
следующая строка — cursor = cursor->next
, что является неопределенным поведением. Вам нужна временная точка на элемент, который вы удаляете, и переместите курсор перед тем, как вы освободитесь.
Вы добавили еще один фрагмент. Вам будет проще помочь, если вы объедините эти фрагменты в один файл программы. Скомпилируйте его, чтобы убедиться, что он работает, и обновите сообщение об ошибке, чтобы оно соответствовало этому файлу. Затем сверните, чтобы нам не приходилось пробираться через код, который работает. минимальный воспроизводимый пример.
@AllanWind Да, вы правы. Два 16 байта были из a1 и a2, которые я забыл освободить. Проблема в двух других блоках по 32 байта. Я обновил фрагменты кода и также включил заголовочный файл.
u_long не объявлен. Вместо этого используйте size_t, кстати.
@AllanWind Ваше предложение создать временный объект, прежде чем я освобожу курсор, устранило проблему. Теперь у меня нет никаких утечек. Спасибо за помощь. В следующий раз я попытаюсь создать MRE вместо публикации фрагментов.
elfree
определенный в main.c нигде не используется. Зачем тебе это? elfree
указатель в структуре списка равен нулю. Вы звоните free
по данным. Где распределяются данные?
@AllanWind u_long из sys/types.h
. В types.h есть typedef для длинных типов.
@н.м. elfree будет предоставленной пользователем функцией для освобождения любых глубоко вложенных структур. Во время инициализации списка пользователь может передать функцию elfree, если у него есть вложенная структура в данных узла, которую необходимо очистить. Цель состоит в том, чтобы предоставить гибкий способ освобождения данных. Если нет вложенных структур и данные могут быть освобождены нормально, elfree может быть NULL. В owl_sll_free в цикле я проверяю, предоставил ли пользователь пользовательскую функцию очистки (elfree) и, если предоставлено, вызывает ее вместо этого.
Вам не нужно говорить мне это. Я понимаю, что он должен делать. Я не спрашиваю тебя об этом. Я вас спрашиваю, что он делает на самом деле, не в каком-то гипотетическом будущем пользовательском коде, а в той программе, которую вы нам показываете. Что делает elfree
в main.c
? (Ничего, нигде не используется). Что делает elfree
внутри списка? (Ничего, это нулевой указатель). Так где же на самом деле выделен указатель, который фактически передается free
? (Вы заполняете ответ).
@н.м. Извини, моя ошибка. Вы абсолютно правы, позвольте мне удалить код из фрагмента.
В main()
вам нужно освободить a1
и a2
.
В 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);
}
Пожалуйста, обновите свой вопрос с помощью программы вместо неполных фрагментов кода. Например, вы не предоставили нам файлы заголовков, поэтому мы не можем скомпилировать ваш код. Два 16 байта, вероятно, являются a1 и a2 в main, которые вы не освобождаете и дублируете в
owl_sll_binsert()