简体   繁体   English

链表程序中的双重释放问题

[英]Double free problem in Linked List program

I have a problem with my free.我的免费有问题。
When I free only l in main.c, without limit function use, its ok.当我在 main.c 中只释放l时,没有限制 function 使用,没关系。
BUT if I put limit function use and I free l_limit , there is the problem: free(): double free detected in tcache 2 and valgrind is not happy.但是,如果我限制 function 使用并且我释放l_limit ,就会出现问题:free(): double free 在 tcache 2 中检测到并且 valgrind 不高兴。 Can you help me to fix the free errors?你能帮我修复免费的错误吗? :) :)

Minimal reproducible example:最小的可重现示例:

#include <stdlib.h>
#include <stdio.h>

typedef void* gpointer;

struct cell_s {
    gpointer ptr_value;
    struct cell_s *next;
};

typedef struct cell_s cell_t;

typedef cell_t* adr; // address

struct list_s {
    cell_t *head;
    int size;
};

typedef struct list_s list_t;

typedef void (*list_gfree)(gpointer data);
typedef void (*list_gprint)(gpointer data);

cell_t* create_cell(gpointer v) {
    cell_t *c = malloc(sizeof(cell_t));
    c->next = NULL;
    c->ptr_value = v;

    return c;
}

void destroy_int(gpointer data) {
    free(data);
}

void print_int(gpointer data) {
    int *ptr_value = (int *)data;
    printf("%d - ", *ptr_value);
}

list_t* list_create() {
    list_t *l = malloc(sizeof(list_t));

    l->head = NULL;
    l->size = 0;

    return l;
}

void list_insert_in_head(list_t *l, gpointer element) {
    adr address_c = create_cell(element);

    address_c->next = l->head;
    l->head = address_c;

    ++l->size;
}

void list_insert_next(list_t *l, gpointer element, adr address) {
    adr address_c = create_cell(element);

    if (l->head == NULL) {
        list_insert_in_head(l, element);
    } else {
        address_c->next = address->next;
        address->next = address_c;
    }

    ++l->size;
} 

void list_remove_in_head(list_t *l, list_gfree ft_destroy) {
    if (l->head != NULL) {
        adr tmp = l->head->next;
        
        ft_destroy(l->head->ptr_value);
        l->head->ptr_value = NULL;
        
        ft_destroy(l->head);
        l->head= tmp;

        --l->size;
    }
}

void list_remove_after(list_t *l, adr address, list_gfree ft_destroy) {
    if (l->head->next == NULL) {
        printf("Use list_remove_in_head function\n");
    } else if (address != NULL) {
        adr tmp = address->next->next;
        
        ft_destroy(address->next->ptr_value);
        address->next->ptr_value = NULL;
        
        ft_destroy(address->next);
        
        address->next = tmp;

        --l->size;
    }
}

void list_destroy(list_t *l, list_gfree ft_destroy) {
    adr current = l->head;

    while(current != NULL) {
        adr tmp = current;

        current = current->next;
        
        ft_destroy(tmp->ptr_value);
        tmp->ptr_value = NULL;
        
        ft_destroy(tmp);
    }

    free(l);
}

void list_print(list_t *l, list_gprint ft_print) {
    adr current = l->head;

    while (current != NULL) {
        ft_print(current->ptr_value);
        current = current->next;
    }

    printf("\n");
}


list_t* limit(list_t *l, int n) {
    list_t *l_limit = list_create();

    adr current = l->head;

    list_insert_in_head(l_limit, current->ptr_value);

    current = current->next;

    adr current_addr_l_limit = l_limit->head;
       
    int count = 1;

    if (n < l->size) {
        while (count < n && current != NULL) {
            ++count;
            
            list_insert_next(l_limit, current->ptr_value, current_addr_l_limit);

            current = current->next;            
            current_addr_l_limit = current_addr_l_limit->next;
        }
    } else {
        while (current != NULL) {
            list_insert_next(l_limit,  current->ptr_value, current_addr_l_limit);

            current = current->next;            
            current_addr_l_limit = current_addr_l_limit->next;
        }
    }

    return l_limit;
}

int main(void) {
    list_t *l = list_create();

    int *ptr_int = (int *)malloc(sizeof(int));
    *ptr_int = 4;
    list_insert_in_head(l, ptr_int);
    list_print(l, print_int);
    printf("Size : %d\n", l->size);

    int *ptr_int_2 = (int *)malloc(sizeof(int));
    *ptr_int_2 = 7;
    list_insert_in_head(l, ptr_int_2);
    list_print(l, print_int);
    printf("Size : %d\n", l->size);

    int *ptr_int_3 = (int *)malloc(sizeof(int));
    *ptr_int_3 = 100;
    list_insert_next(l, ptr_int_3, l->head);
    list_print(l, print_int);
    printf("Size : %d\n", l->size);

    list_t *l_limit = limit(l, 2);
    printf("\nLIMIT 2 \n");
    list_print(l_limit, print_int);
    printf("\n");

    list_remove_in_head(l, destroy_int);
    list_print(l, print_int);
    printf("Size : %d\n", l->size);

    list_remove_after(l, l->head, destroy_int);
    list_print(l, print_int);
    printf("Size : %d\n", l->size);

    list_remove_after(l, l->head, destroy_int);
    list_print(l, print_int);
    printf("Size : %d\n", l->size);

    int *ptr_int_4 = (int *)malloc(sizeof(int));
    *ptr_int_4 = 447;
    list_insert_next(l, ptr_int_4, l->head);
    list_print(l, print_int);
    printf("Size : %d\n", l->size);
    
    list_destroy(l_limit, destroy_int);
    list_destroy(l, destroy_int);
}

Output: Output:

4 - 
Size : 1
7 - 4 - 
Size : 2
7 - 100 - 4 - 
Size : 3

LIMIT 2 
7 - 100 - 

100 - 4 - 
Size : 2
100 - 
Size : 1
Use list_remove_in_head function.
100 - 
Size : 1
100 - 447 - 
Size : 2
free(): double free detected in tcache 2

Execution: (-g -fsanitize=address)执行: (-g -fsanitize=地址)

=================================================================
==16065==ERROR: AddressSanitizer: attempting double-free on 0x602000000070 in thread T0:
    #0 0x7f8b09173517 in __interceptor_free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:127
    #1 0x55ad7141f365 in destroy_int /home/zzz/zzz/main2.c:34
    #2 0x55ad7141fa5b in list_destroy /home/zzz/zzz/main2.c:112
    #3 0x55ad714203a9 in main /home/zzz/zzz/main2.c:211
    #4 0x7f8b08ec4fcf in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #5 0x7f8b08ec507c in __libc_start_main_impl ../csu/libc-start.c:409
    #6 0x55ad7141f204 in _start (/home/zzz/zzz/main+0x1204)

0x602000000070 is located 0 bytes inside of 4-byte region [0x602000000070,0x602000000074)
freed by thread T0 here:
    #0 0x7f8b09173517 in __interceptor_free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:127
    #1 0x55ad7141f365 in destroy_int /home/zzz/zzz/main2.c:34
    #2 0x55ad7141f6ea in list_remove_in_head /home/antoine/progc/main2.c:77
    #3 0x55ad714200f5 in main /home/zzz/zzz/main2.c:193
    #4 0x7f8b08ec4fcf in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58

previously allocated by thread T0 here:
    #0 0x7f8b09173867 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:145
    #1 0x55ad7141feed in main /home/zzz/zzz/main2.c:176
    #2 0x7f8b08ec4fcf in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58

SUMMARY: AddressSanitizer: double-free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:127 in __interceptor_free
==16065==ABORTING

Valgrind瓦尔格林德

==16161== Invalid free() / delete / delete[] / realloc()
==16161==    at 0x484621F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==16161==    by 0x10921F: destroy_int (in /home/zzz/zzz/main2)
==16161==    by 0x1094C2: list_destroy (in /home/zzz/zzz/main2)
==16161==    by 0x109918: main (in /home/zzz/zzz/main2)
==16161==  Address 0x4a97570 is 0 bytes inside a block of size 4 free'd
==16161==    at 0x484621F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==16161==    by 0x10921F: destroy_int (in /home/zzz/zzz/main2)
==16161==    by 0x10939C: list_remove_in_head (in /home/antoine/progc/main2)
==16161==    by 0x1097CA: main (in /home/zzz/zzz/main2)
==16161==  Block was alloc'd at
==16161==    at 0x4843839: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==16161==    by 0x1096B7: main (in /home/zzz/zzz/main2)
==16161== 
==16161== Invalid free() / delete / delete[] / realloc()
==16161==    at 0x484621F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==16161==    by 0x10921F: destroy_int (in /home/zzz/zzz/main2)
==16161==    by 0x1094C2: list_destroy (in /home/zzz/zzz/main2)
==16161==    by 0x10992E: main (in /home/zzz/zzz/main2)
==16161==  Address 0x4a97610 is 0 bytes inside a block of size 4 free'd
==16161==    at 0x484621F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==16161==    by 0x10921F: destroy_int (in /home/zzz/zzz/main2)
==16161==    by 0x1094C2: list_destroy (in /home/zzz/zzz/main2)
==16161==    by 0x109918: main (in /home/zzz/zzz/main2)
==16161==  Block was alloc'd at
==16161==    at 0x4843839: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==16161==    by 0x109715: main (in /home/zzz/zzz/main2)
==16161== 
==16161== 
==16161== HEAP SUMMARY:
==16161==     in use at exit: 0 bytes in 0 blocks
==16161==   total heap usage: 13 allocs, 15 frees, 1,168 bytes allocated
==16161== 
==16161== All heap blocks were freed -- no leaks are possible
==16161== 
==16161== For lists of detected and suppressed errors, rerun with: -s
==16161== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

Your limit fuction is:你的极限功能是:

list_t* limit(list_t *l, int n) {
list_t *l_limit = list_create();

adr current = l->head;

list_insert_in_head(l_limit, current->ptr_value);//Creates a new cell but uses the same ptr_value!

current = current->next;

adr current_addr_l_limit = l_limit->head;
   
int count = 1;

if (n < l->size) {
    while (count < n && current != NULL) {
        ++count;
        
        list_insert_next(l_limit, current->ptr_value, current_addr_l_limit); //reuses the same ptr_value!

        current = current->next;            
        current_addr_l_limit = current_addr_l_limit->next;
    }
} else {
    while (current != NULL) {
        list_insert_next(l_limit,  current->ptr_value, current_addr_l_limit);

        current = current->next;            
        current_addr_l_limit = current_addr_l_limit->next;
    }
}

return l_limit;
}

When inserting elements in l_limits from the source list, you do create new cells, but you don't create new elements!从源列表中l_limits中插入元素时,确实会创建新单元格,但不会创建新元素!
So the cells in the original list use the same ptr_value as the cells in the new list!所以原始列表中的单元格使用与新列表中的单元格相同的ptr_value

Thus when you destroy the second list, you attempt to free the same ptr_values:因此,当您销毁第二个列表时,您会尝试释放相同的 ptr_values:

void list_destroy(list_t *l, list_gfree ft_destroy) {
adr current = l->head;

while(current != NULL) {
    adr tmp = current;

    current = current->next;
    
    ft_destroy(tmp->ptr_value);//When this is called for the second list, you access the same pointer as for the first list!
    tmp->ptr_value = NULL;
    
    ft_destroy(tmp);
}

free(l);
}

To solve this issue, you could make actual copies of the object stored at "ptr_values" in limit , but that requires you to know the type of the value stored in ptr_value:要解决此问题,您可以制作 object 的实际副本,该副本存储在limit的“ptr_values”中,但这需要您知道 ptr_value 中存储的值的类型:

list_t* limit(list_t *l, int n) {
list_t *l_limit = list_create();

adr current = l->head;
gpointer buff = malloc(sizeof(int));//replace int by the correct type
if (buff == NULL)
   exit(-1);
*((int*)buff) = *((int*)current->ptr_value);
list_insert_in_head(l_limit, buff);

current = current->next;

adr current_addr_l_limit = l_limit->head;
   
int count = 1;

if (n < l->size) {
    while (count < n && current != NULL) {
        ++count;
        gpointer buff = malloc(sizeof(int));//replace int by the correct type
        if (buff == NULL)
          exit(-1);
        *buff = *current->ptr_value;
        list_insert_next(l_limit, buff, current_addr_l_limit);

        current = current->next;            
        current_addr_l_limit = current_addr_l_limit->next;
    }
} else {
    while (current != NULL) {
        list_insert_next(l_limit,  current->ptr_value, current_addr_l_limit);

        current = current->next;            
        current_addr_l_limit = current_addr_l_limit->next;
    }
}

return l_limit;
}

Alternatively you could make a special list destructor that doesn't free the content of the cells, but that feels a bit weird.或者,您可以制作一个特殊的列表析构函数,它不会释放单元格的内容,但这感觉有点奇怪。

You allocate memory for the data elements using malloc in main and store the pointer in your list.您在main中使用malloc为数据元素分配 memory 并将指针存储在列表中。

Later you call free for the data inside list management functions like list_destroy and list_remove_in_head .稍后,您可以free调用list_destroylist_remove_in_head等列表管理函数中的数据。

Function limit creates a new list and stores pointers to the data from the original list in the new list. Function limit创建一个新列表并将指向原始列表中数据的指针存储在新列表中。 Then you will have duplicate (or multiple) pointers to data elements in different lists (in this case l and l_limit ), and when the list management functions free the memory of the data elements this will result in double (or multiple) free .然后,您将拥有指向不同列表中数据元素的重复(或多个)指针(在本例中为ll_limit ),并且当列表管理函数释放 memory 的数据元素时,这将导致 double (或多个) free

The concept to allocate the memory in main , or more general in the caller of your list management functions and to free it inside your functions does not work when your functions can create duplicate or multiple pointers to the same memory object.当您的函数可以创建指向同一个 memory ZA8CFDE6331B49EB2AC96F96666666666666666666666666666666666666666666666666666666666F06CD818D7BF3D61980E291ZF06CD818D7BF3D61980E2957F06CD818D7BF3D61980E291Z 的概念时,分配main的概念,或者在您的列表管理函数的调用者中更一般,并在您的函数中释放它时,当您的函数可以创建指向相同 memory

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM