释放已使用数据的内存会导致分段错误
Posted
技术标签:
【中文标题】释放已使用数据的内存会导致分段错误【英文标题】:Freeing memory of used data leads to Segmentation Fault 【发布时间】:2012-06-15 21:04:28 【问题描述】:我写了一个哈希表,它基本上由这两种结构组成:
typedef struct dictEntry
void *key;
void *value;
struct dictEntry *next;
dictEntry;
typedef struct dict
dictEntry **table;
unsigned long size;
unsigned long items;
dict;
dict.table
是一个多维数组,包含了所有存储的键/值对,又是一个链表。
如果哈希表的一半已满,我将其扩大一倍并重新散列:
dict *_dictRehash(dict *d)
int i;
dict *_d;
dictEntry *dit;
_d = dictCreate(d->size * 2);
for (i = 0; i < d->size; i++)
for (dit = d->table[i]; dit != NULL; dit = dit->next)
_dictAddRaw(_d, dit);
/* FIXME memory leak because the old dict can never be freed */
free(d); // seg fault
return _d;
上面的函数使用旧哈希表中的指针并将其存储在新创建的哈希表中。释放旧的dict d
时会发生分段错误。
我怎样才能释放旧的哈希表结构而不必再次为键/值对分配内存?
为了完整性,编辑:
dict *dictCreate(unsigned long size)
dict *d;
d = malloc(sizeof(dict));
d->size = size;
d->items = 0;
d->table = calloc(size, sizeof(dictEntry*));
return d;
void dictAdd(dict *d, void *key, void *value)
dictEntry *entry;
entry = malloc(sizeof *entry);
entry->key = key;
entry->value = value;
entry->next = '\0';
if ((((float)d->items) / d->size) > 0.5) d = _dictRehash(d);
_dictAddRaw(d, entry);
void _dictAddRaw(dict *d, dictEntry *entry)
int index = (hash(entry->key) & (d->size - 1));
if (d->table[index])
dictEntry *next, *prev;
for (next = d->table[index]; next != NULL; next = next->next)
prev = next;
prev->next = entry;
else
d->table[index] = entry;
d->items++;
【问题讨论】:
不清楚;你的问题是“为什么我会出现段错误?”? 在任何地方使用 _underscores 会使你的代码 _very _hard _to _read。 是的,实际上,这就是为什么我会出现 seg 错误!会尽快改变。 那么 dictCreate 是做什么的呢?发布代码;-) 【参考方案1】:为了阐明 Graham 的观点,您需要注意在这个库中内存是如何被访问的。用户有一个指向其字典的指针。当您重新哈希时,您释放了该指针引用的内存。虽然你为他们分配了一个新的字典,但新的指针永远不会返回给他们,所以他们不知道不要使用旧的。当他们再次尝试访问他们的字典时,它指向的是已释放的内存。
一种可能性是不要完全丢弃旧字典,而只丢弃您在字典中分配的 dictEntry 表。这样,您的用户将永远不必更新他们的指针,但您可以重新调整表以适应更有效的访问。试试这样的:
void _dictRehash(dict *d)
printf("rehashing!\n");
int i;
dictEntry *dit;
int old_size = d->size;
dictEntry** old_table = d->table;
int size = old_size * 2;
d->table = calloc(size, sizeof(dictEntry*));
d->size = size;
d->items = 0;
for (i = 0; i < old_size; i++)
for (dit = old_table[i]; dit != NULL; dit = dit->next)
_dictAddRaw(d, dit);
free(old_table);
return;
顺便说一句,我不确定你的哈希函数是做什么的,但在我看来,这条线
int index = (hash(entry->key) & (d->size - 1));
有点不正统。你得到一个哈希值并按位和表的大小进行操作,我想这在某种意义上是有效的,因为它可以保证在(我认为?)[0, max_size)
之内,我想你可能是指%
模数。
【讨论】:
我认为这非常正确 - 但还有另一个问题:_dictAddRaw
添加了 整个链 条目,因为条目的 next
字段从未设置为 NULL (并且要小心它在哪里被 NULL 化,因为你需要它在 for
循环中。所以重建的表将是一团相互链接的链......此外,& (size-1)
应该工作假设表大小始终是 2 的幂。但是模数要好得多!【参考方案2】:
-
调试此问题的最佳方法是针对 valgrind 运行您的代码。
但是给你一些观点:
当您 free(d)
时,您期望在您的 struct dict
上调用更多的 destructor
,这将在内部释放分配给指向 dictEntry
的指针的内存
为什么要删除整个has表才能展开呢?你有一个next
指针,为什么不直接向它附加新的哈希条目呢?
解决方案不是释放d
,而是通过分配更多struct dictEntry
并将它们分配给适当的next
来扩展d
。
当收缩d
时,您将不得不遍历next
以到达末尾,然后开始为struct dictEntry
s 释放d
内部的内存。
【讨论】:
谢谢,你的第一点就是解决方案。扩展列表不是想要的行为。 如果能解决您的问题,投赞成票并接受答案会很好:D 还没有完全解决。如果我不能释放旧的字典,那就是内存泄漏,对吧?有什么解决方案可以避免这种情况?【参考方案3】:dictCreate
究竟做了什么?
我认为您对(固定大小的)dict
对象和指向dictEntries
中的dictEntries
的指针数组(可能是可变大小的)dict.table
感到困惑。
也许你可以只 realloc()
dict.table
指向的内存,而不是创建一个新的“dict”对象并释放旧的对象(顺便说一句,这并没有释放 dictentries 表!)
【讨论】:
我避免使用 realloc 因为它应该很慢。但是,我知道条目没有被释放 - 指针只是在新的哈希表中再次使用。 这就是所谓的“过早优化”。无论如何,速度是您目前最不担心的问题。【参考方案4】:您正在释放一个传递给您的函数的指针。仅当您知道调用您的函数的人并未仍在尝试使用 d
的旧值时,这才是安全的。检查所有调用_dictRehash()
的代码并确保没有任何东西挂在旧指针上。
【讨论】:
Hmmn,我调用dictAdd(d, key, value),它检查hashtable是否已满,然后调用_dictRehash(),会不会是这个问题? 是的,除非dictAdd()
将新的字典指针返回给它的调用者。以上是关于释放已使用数据的内存会导致分段错误的主要内容,如果未能解决你的问题,请参考以下文章