嵌套TAILQ双重免费错误

时间:2015-10-28 17:11:28

标签: c

以下代码是更大代码库中问题的最小示例。它可以通过简单的gcc -g main.c编译。

我有一个项目列表,每个项目都包含一个属性列表。目标是从一些输入中解析一个列表,并且为了只在列表中完成解析的项目,它使用一个临时项目,一旦完全解析就添加到列表中。

由于解析定期发生,因此项目列表必须在某个时候是免费的。代码,就目前来说,在第49行产生“双重自由或腐败”。我的问题,简单地说,是:为什么会发生这种情况/我做错了什么以及如何解决它?我似乎无法绕过记忆被破坏的地方。

的main.c

#include <sys/queue.h>
#include <stdlib.h>
#include <string.h>

typedef struct attribute_t {
    char *name;
    TAILQ_ENTRY(attribute_t) attributes;
} attribute_t;

typedef struct item_t {
    char *name;
    TAILQ_HEAD(attributes_head, attribute_t) attributes;

    TAILQ_ENTRY(item_t) items;
} item_t;

TAILQ_HEAD(items_head, item_t) items = TAILQ_HEAD_INITIALIZER(items);
static item_t item_builder;

static void read_item_start(const char *name) {
    TAILQ_INIT(&(item_builder.attributes));
    item_builder.name = strdup(name);
}

static void read_item_end() {
    item_t *new_item = calloc(1, sizeof(item_t));
    memcpy(new_item, &item_builder, sizeof(item_t));
    TAILQ_INSERT_TAIL(&items, new_item, items);

    memset(&item_builder, '\0', sizeof(item_t));
}

static void read_item_attribute(const char *name) {
    attribute_t *new = calloc(1, sizeof(attribute_t));
    new->name = strdup(name);
    TAILQ_INSERT_TAIL(&(item_builder.attributes), new, attributes);
}

static void free_items() {
    item_t *item;
    while (!TAILQ_EMPTY(&items)) {
        item = TAILQ_FIRST(&items);
        free(item->name); // <-- crash

        attribute_t *attribute;
        while (!TAILQ_EMPTY(&(item->attributes))) {
            attribute = TAILQ_FIRST(&(item->attributes));
            free(attribute->name);

            TAILQ_REMOVE(&(item->attributes), attribute, attributes);
            free(attribute);
        }

        TAILQ_REMOVE(&items, item, items);
        free(item);
    }
}

int main() {
    read_item_start("first item");
    read_item_attribute("first attribute");
    read_item_attribute("second attribute");
    read_item_end();

    read_item_start("second item");
    read_item_attribute("first attribute");
    read_item_attribute("second attribute");
    read_item_end();

    free_items();

    return 0;
}

2 个答案:

答案 0 :(得分:1)

我在每次分配后都插入了printfs并且释放了,这就是结果:

strdup = 0x800e15000
read_item_attribute: calloc = 0x800e20000
read_item_attribute: calloc = 0x800e20020
read_item_end: calloc = 0x800e21000
strdup = 0x800e15020
read_item_attribute: calloc = 0x800e20060
read_item_attribute: calloc = 0x800e20080
read_item_end: calloc = 0x800e21030
free_items(name): 0x800e15000
free_items(attribute->name): 0x800e15010
free_items(attribute): 0x800e20000
free_items(attribute->name): 0x5a5a5a5a5a5a5a5a

观察free_items(attribute->name): 0x800e15010已经在尝试释放未分配的内容。显然,

attribute = TAILQ_FIRST(&(item->attributes));

不会为您提供具有已分配name成员的结构。

答案 1 :(得分:1)

我认为问题是TAILQ_INIT初始化tqh_last是指向tqh_first的指针:

#define TAILQ_INIT(head)                   \
do {                                       \
    (head)->tqh_first = NULL;              \
    (head)->tqh_last = &(head)->tqh_first; \
} while (0)

&(head)->tqh_first相当于稍微清晰&((head)->tqh_first))。

因此,当您使用memcpy时,违反了tqh_last指向tqh_first的内存位置的不变量。

我认为干净的解决方案是在复制的结构中初始化一个新的TAILQ,并将旧列表中的所有元素移动到新的TAILQ中。

稍微不那么干净但效率更高的解决方案是直接修复指针。