【问题标题】:segfault in a C garbage collectorC 垃圾收集器中的段错误
【发布时间】:2014-09-24 00:56:02
【问题描述】:

我正在用 C 语言构建一个简单的垃圾收集器,带有一个 void 指针链表,用于收集分配的指针并在最后释放它们。

#include "linked_list.h"

#define MAKE_GC(NAME) \
MAKE_LIST(NAME); \
static void _throw_away_##NAME() { \
    ITERATOR(NAME) = &NAME; \
    do { \
        free(ITERATOR(NAME)->elem); \
    } while ((ITERATOR(NAME) = ITERATOR(NAME)->next) != NULL); \
    DESTROY_LIST(NAME); \
}

#define GC_ALLOC(TYPE, TARGET, LEN, GC_NAME) \
do { \
    TARGET = (TYPE *)malloc(LEN * sizeof(TYPE)); \
    PUSH(TYPE *, TARGET, GC_NAME); \
} while (0)

#define GC_FREE(NAME) _throw_away_##NAME()

上面是垃圾回收器,下面是linked_list.h

struct linked_list {
    void *elem;
    struct linked_list *next;
};

#define ITERATOR(LIST_NAME) _iter_##LIST_NAME

#define MAKE_LIST(NAME) \
struct linked_list NAME = { NULL, NULL }; \
struct linked_list *ITERATOR(NAME) = &NAME

#define PUSH(TYPE, X, LIST) \
do { \
    ITERATOR(LIST) = ITERATOR(LIST)->next = (struct linked_list *)malloc(sizeof(struct linked_list)); \
    *(TYPE *)ITERATOR(LIST)->elem = X; \
    ITERATOR(LIST)->next = NULL; \
} while (0)

#define DESTROY_LIST(LIST) \
do { \
    struct linked_list *l; \
    ITERATOR(LIST) = &LIST; \
    do { \
        l = ITERATOR(LIST)->next; \
        free(ITERATOR(LIST)); \
    } while ((ITERATOR(LIST) = l) != NULL); \
} while (0)

当我使用以下代码测试此代码时,

#include <stdio.h>
#include "garbage_collector.h"

MAKE_GC(char_gc);

int main() {
    char *str;
    int i;

    GC_ALLOC(char, str, 11, char_gc);
    for (i = 0; i < 10; i++) {
        putchar(str[i] = i + '0');
    }
    str[i] = '\0';
    putchar('\n');
    puts(str);

    GC_FREE(char_gc);
    return 0;
}

它按预期运行,尽管调试器(gdb 和 Visual Studio 调试器)不断在 GC_ALLOC 中抛出段错误。这是一段非常短的代码,我很生气,我仍然不知道哪里出错了。

我想确定我的程序在哪里出现问题并在其他地方实施之前修复它。提前感谢您的帮助。

【问题讨论】:

  • 你在哪里包含stdlib.h?你为什么要转换malloc的返回值?
  • stdlib.h 应该包含在两个头文件中,这肯定不会导致问题。我使用 malloc 是因为我经常使用 c++ 编译器进行编译,尤其是 Visual Studio。

标签: c garbage-collection segmentation-fault


【解决方案1】:

在您的 PUSH 宏中,我认为这行不正确:

*(TYPE *)ITERATOR(LIST)->elem = X; \

我想你想要这样的东西:

ITERATOR(LIST)->elem = (void *)X; \

在您的代码中,我在这里遇到了段错误:

GC_ALLOC(char, str, 11, char_gc);

这是因为GC_ALLOC 执行PUSH,将str 作为X 传递。一旦str 已被上一行正确分配,您希望将指针分配给elem,但是您的代码尝试取消引用 elem,然后将指针分配给该取消引用项。取消引用 elem 在那个时候是非法的,并且 seg 错误,因为它还没有被分配(并且不应该在那个时候)。

之后,您的代码中最后的GC_FREE 出现问题,因为它最终会尝试释放未分配的指针。当GC_FREE 尝试DESTROY_LIST(gc_Char) 时,它会从列表中的第一项开始。但是这个项目不是通过malloc分配的(看看MAKE_LIST),所以它不能使用free释放。所以当我们想要DESTROY_LIST 时,我们必须跳过列表中的第一项。以下代码修复了这些项目,并且似乎可以在您的测试用例中正常工作:

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

struct linked_list {
    void *elem;
    struct linked_list *next;
};

#define ITERATOR(LIST_NAME) _iter_##LIST_NAME

#define MAKE_LIST(NAME) \
struct linked_list NAME = { NULL, NULL }; \
struct linked_list *ITERATOR(NAME) = &NAME

#define PUSH(TYPE, X, LIST) \
do { \
    ITERATOR(LIST) = ITERATOR(LIST)->next = (struct linked_list *)malloc(sizeof(struct linked_list)); \
    ITERATOR(LIST)->elem = (void *)X; \
    ITERATOR(LIST)->next = NULL; \
} while (0)

#define DESTROY_LIST(LIST) \
do { \
    struct linked_list *l; \
    ITERATOR(LIST) = &LIST; \
    l = ITERATOR(LIST)->next; \
    while ((ITERATOR(LIST) = l) != NULL) { \
        l = ITERATOR(LIST)->next; \
        free(ITERATOR(LIST)); \
    }  \
} while (0)

#define MAKE_GC(TYPE, NAME) \
MAKE_LIST(NAME); \
static void _throw_away_##NAME() { \
    ITERATOR(NAME) = &NAME; \
    do { \
        free(ITERATOR(NAME)->elem); \
    } while ((ITERATOR(NAME) = ITERATOR(NAME)->next) != NULL); \
    DESTROY_LIST(NAME); \
}

#define GC_ALLOC(TYPE, TARGET, LEN, GC_NAME) \
do { \
    TARGET = (TYPE *)malloc(LEN * sizeof(TYPE)); \
    PUSH(TYPE *, TARGET, GC_NAME); \
} while (0)

#define GC_FREE(NAME) _throw_away_##NAME()

MAKE_GC(char, char_gc);

int main() {
    char *str;
    int i;
    GC_ALLOC(char, str, 11, char_gc);
    for (i = 0; i < 10; i++) {
        putchar(str[i] = i + '0');
    }
    str[i] = '\0';
    putchar('\n');
    puts(str);
    GC_FREE(char_gc);
    return 0;
}

【讨论】:

  • 我还是不明白为什么*(TYPE *)ITERATOR(LIST)-&gt;elem = X; 是一个错误的行。如果TYPE 不是指针而是intchar 或其他什么,它可以正常工作。此外,DESTROY_LIST 工作正常,因为 MAKE_LIST 始终将第一个元素初始化为 NULL
猜你喜欢
  • 2013-04-01
  • 1970-01-01
  • 2017-02-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多