【问题标题】:Free generic linked list in C - segfaultC中的免费通用链表 - segfault
【发布时间】:2016-10-02 23:02:14
【问题描述】:

我正在用 C 编写一个通用链表(遵循 Kyle Loudon 的书), 但是在释放它时,我遇到了 segfault

用于列表定义的数据类型:

typedef struct list_elem_
{
    void                *data;
    struct list_elem_   *next;
} list_elem;

typedef struct link_list_
{
    int         size;
    int         (*match)(const void *key1, const void *key2);
    void            (*destroy)(void *data);
    list_elem       *head;
    list_elem       *tail;
} link_list;

用于销毁调用者数据的函数:

void destroy_data(void *data)
{
    if(data)
        free(data);

    return;
}

函数指针传递的销毁:

void list_init(link_list *list, void (*destroy)(void *data))
{
    list->size = 0;
    list->destroy = destroy;
    list->head = NULL;
    list->tail = NULL;

    return;
}

释放列表:

void list_destroy(link_list *list)
{
    void* data;

    while(list_size(list) > 0)
        if(list_rem_next(list, NULL, (void**)&data) == 0 && list->destroy != NULL)
            list->destroy(data);

    memset(list,0,sizeof(link_list));

    return;
}

段错误是由destroy_data中的free触发的。

============== 编辑 =====================

删除列表元素

int list_rem_next(link_list *list, list_elem *element, void **data)
{
    list_elem *OldElement;

    if(list_size(list) ==0)
        return -1;

    /* Remove the head */
    if(element == NULL)
    {
        *data = list->head->data;
        OldElement = list->head;
        list->head = list->head->next;

        if(list_size(list) == 1)
            list->tail = NULL;

    /* Remove other than head */
    } else {
        if(element->next == NULL)
            return -1;

        *data = element->data;
        OldElement = element->next;
        element->next = element->next->next;

        if(element->next == NULL)
            list->tail = element;
    }

    free(OldElement);

    list->size--;

    return 0;
}

=================== 编辑 2 ==========================

主目录

link_list   myList;
int i;
int *iptr;
char *chrPtr;

list_init(&myList, destroy_data);

for(i = 0; i < 4; i++)
{
    iptr = malloc(sizeof(int));
    *iptr = i;
    list_ins_next(&myList, NULL, iptr);
}

chrPtr = malloc(sizeof("uno\0"));
chrPtr = "uno\0";
list_ins_next(&myList,NULL,chrPtr);

chrPtr = malloc(sizeof("stringa numero due\0"));
chrPtr = "stringa numero due\0";
list_ins_next(&myList,NULL,chrPtr);

chrPtr = NULL;
iptr = NULL;

getchar();

list_destroy(&myList);

【问题讨论】:

  • void destroy_data(void *data) { if(data) free(data); return; } :: 烧书。
  • 发布list_rem_next的定义
  • list_rem_next的定义是什么
  • 您的免费代码可能没有任何问题。您的免费代码假定列表已正确构建,如果确实如此,那很好。这就是为什么发布一个展示问题的完整示例很重要——它可能不是您认为的位置。见sscce.org
  • 啊啊啊啊啊啊! chrPtr = malloc(sizeof("uno\0")); chrPtr = "uno\0"; —— (a) 当 C 自动在它后面加一个时,为什么显式的 \0? (b) 你能说“内存泄漏”吗? (c) strcpy() 发生了什么事?您将未分配的内存指针传递给free();实际上,您将指向字符串常量的指针传递给free()。这是未定义的行为,很容易导致崩溃!这也是 Greg 提到的 MCVE (minimal reproducible example) 又名 SSCCE(简短、独立、正确的示例)如此重要的原因。问题不在于您最初显示的代码;它在另一个代码中。

标签: c linked-list segmentation-fault free generic-programming


【解决方案1】:

main() 的代码中,您有:

chrPtr = malloc(sizeof("uno\0"));
chrPtr = "uno\0";
  1. 当 C 自动在其后添加一个 \0 时,为什么要显式显示 \0
  2. 你能说“内存泄漏”吗?你分配内存;您立即通过将字符串文字的地址分配给同一指针来覆盖指向该分配内存的唯一指针。
  3. strcpy() 怎么了?

由于这种滥用,您将未分配的内存指针传递给free();实际上,您将指向字符串常量的指针传递给free()。这是未定义的行为,很容易导致崩溃!

问题不在于您最初显示的代码;它在另一个代码中。这也是为什么 MCVE (Minimal, Complete, Verifiable Example) — 又名 SSCCE (Short, Self-Contained, Correct Example) mentioned by Greg Hewgill — 如此重要的原因。我们无法调试您未显示的代码 - 确定问题不在您显示的代码中是不必要的艰苦工作。

你可能会使用:

chrPtr = strdup("uno"));
list_ins_next(&myList, NULL, chrPtr);

chrPtr = strdup("stringa numero due");
list_ins_next(&myList,NULL,chrPtr);

避免麻烦。如果做不到这一点,您可以使用:

chrPtr = malloc(sizeof("uno"));
strcpy(chrPtr, "uno");
list_ins_next(&myList, NULL, chrPtr);

chrPtr = malloc(sizeof("stringa numero due"));
strcpy(chrPtr, "stringa numero due");
list_ins_next(&myList,NULL,chrPtr);

这些都没有检查内存分配是否成功;这也应该在生产代码中完成,并且可以说是在学校作业中。

注意sizeof("string literal")计算空字节,所以长度是正确的。同样注意strlen("string literal") 不计算空字节——小心!

代码中可能还有其他问题;我还没有验证一切都是干净的。但是这部分会更干净,更有可能正常工作。


函数list_size()list_ins_next() 未显示。大小可以猜测; list_ins_next() 没那么容易。

我还观察到代码将 4 个整数和 2 个字符串插入到列表中。没有办法知道那是事后插入的内容。 main() 中的代码非常不通用。支持代码可以处理它——但异构列表很棘手;在没有遇到此类问题之前不要尝试。一个整数列表;美好的。一个字符串列表;美好的。一个整数和字符串列表——狡猾!

【讨论】:

  • 非常感谢乔纳森!就是这样!还有很多东西要学……下次我保证我会用 SSCCE 发表一篇更易读的文章
  • 学习是我们所要求的——不要重复同样的……错误。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-24
  • 2017-08-08
  • 2019-07-29
  • 1970-01-01
  • 2021-11-17
  • 2015-05-05
相关资源
最近更新 更多