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

C 释放已用数据的内存会导致分段错误,c,pointers,hashtable,resize,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是一个多维数组,其中包

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

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

您正在释放传递给函数的指针。只有当您知道调用您函数的人不再尝试使用旧值
d
时,这才是安全的。检查所有调用
\u dictRehash()
的代码,确保旧指针上没有挂起任何东西

  • 最好的调试方法是针对valgrind运行代码
  • 但请给出一些观点:

  • 当您
    free(d)
    时,您希望对
    struct dict
    调用更多的
    析构函数
    ,这将在内部释放分配给指向
    dictEntry的指针的内存

  • 为什么要删除整个has表才能展开它?你有一个
    next
    指针,为什么不直接向它添加新的散列项呢

  • 解决方案不是释放
    d
    ,而是通过分配更多
    struct dictEntry
    并将它们分配给适当的
    next
    来扩展
    d


    收缩
    d
    时,您必须迭代
    next
    以到达末尾,然后开始释放
    d
    struct dictEntry
    s的内存

    dictCreate
    实际上做什么

    我认为您正在混淆(固定大小)
    dict
    对象和
    dict.table
    中指向
    dictEntries
    的指针数组(可能大小可变)


    也许您可以只
    realloc()
    dict.table指向的内存,而不是创建一个新的“dict”对象并释放旧的对象(顺便说一句,这并不是释放dict条目表!)

    为了阐明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;
    
    }
    

    这有点离经叛道。你得到一个散列值,然后用表的大小按位进行and运算,我想这在某种意义上是有效的,它保证在(我想?
    [0,max_size)
    之内,我想你可能是指模的
    %

    不清楚;你的问题是“我为什么会出现seg错误?”?到处使用下划线会让你的代码很难阅读。是的,事实上,这就是为什么我会出现seg错误!会尽快改变。dictCreate做什么,然后?发布代码;-)嗯,我调用dictAdd(d,key,value),它会检查哈希表是否已满,然后调用dictRehash(),这可能是问题吗?是的,除非
    dictAdd()
    将新的dict指针返回给它的调用者。我避免使用realloc,因为它应该非常慢。但是,我知道这些条目并没有被释放-指针只是在新的哈希表中再次使用。这被称为“过早优化”。无论如何,目前速度是您最不担心的。谢谢,您的第一点是sol注意。扩大列表并不是你想要的行为。如果这能解决你的问题,投票并接受答案会很好:DIt还没有完全解决。如果我不能释放旧的dict,这就是内存泄漏,对吗?有什么解决方案可以避免这一点?我想这是非常正确的-但还有另一个问题:
    \u dictAddRaw
    补充道整个条目链,因为条目的
    next
    字段永远不会设置为NULL(注意它为NULL的位置,因为在
    for
    循环中需要它。因此重建的表将是一堆相互链接的链…还有
    &(size-1)
    假设表格大小始终为2的幂,则应能正常工作。但模数更好!
    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));