释放已使用数据的内存会导致分段错误

时间:2012-06-12 22:35:18

标签: c pointers hashtable resize

我写了一个哈希表,它基本上由这两个结构组成:

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++;
}

4 个答案:

答案 0 :(得分:3)

  1. 调试此方法的最佳方法是针对valgrind运行代码。
  2. 但是给你一些看法:

    1. 当你free(d)期望destructor更多struct dict来自dictEntry时,会在内部释放分配给指向next指针的指针的内存

    2. 为什么要删除整个has表来展开它?你有一个d指针,为什么不直接添加新的哈希条目呢?

    3. 解决方案不是释放d,而是通过分配更多struct dictEntry并将其分配给适当的next来展开d

      签约next时,您必须迭代struct dictEntry才能到达终点,然后开始释放d内{{1}}内存的内存。

答案 1 :(得分:3)

为了澄清格雷厄姆的观点,你需要注意在这个库中如何访问内存。用户有一个指向其字典的指针。重新散列时,释放该指针引用的内存。虽然您为它们分配了一个新字典,但新指针永远不会返回给它们,因此它们不知道不使用旧字典。当他们再次尝试访问他们的字典时,它指向释放的内存。

一种可能性是不要完全丢弃旧字典,而只删除在字典中分配的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),我想你可能意味着{{} 1}}表示模数。

答案 2 :(得分:1)

您正在释放传入您的函数的指针。只有当您知道调用您的函数的任何人仍在尝试使用d的旧值时,这才是安全的。检查调用_dictRehash()的所有代码,并确保没有任何内容挂在旧指针上。

答案 3 :(得分:1)

dictCreate实际上做了什么?

我认为你在(固定大小)dict对象与dictEntries中指向dict.table的(可能是可变大小的)指针数组之间感到困惑。

也许你只能realloc() dict.table所指向的内存,而不是创建一个新的'dict'对象并释放旧的对象(顺便说一句,这并不是在释放指责表。 !)