以下代码是更大代码库中问题的最小示例。它可以通过简单的gcc -g main.c
编译。
我有一个项目列表,每个项目都包含一个属性列表。目标是从一些输入中解析一个列表,并且为了只在列表中完成解析的项目,它使用一个临时项目,一旦完全解析就添加到列表中。
由于解析定期发生,因此项目列表必须在某个时候是免费的。代码,就目前来说,在第49行产生“双重自由或腐败”。我的问题,简单地说,是:为什么会发生这种情况/我做错了什么以及如何解决它?我似乎无法绕过记忆被破坏的地方。
#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;
}
答案 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中。
稍微不那么干净但效率更高的解决方案是直接修复指针。