簡體   English   中英

釋放已使用數據的內存會導致分段錯誤

[英]Freeing memory of used data leads to Segmentation Fault

我寫了一個哈希表,它基本上由這兩個結構組成:

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++;
}
  1. 調試它的最佳方法是針對valgrind運行代碼。

但是給你一些看法:

  1. 當你free(d)你期望在你的struct dict上有更多的destructor調用,它會在內部釋放分配給指向dictEntry的指針的內存

  2. 為什么你要刪除整個has表來擴展它? 你有一個next指針,為什么不直接添加新的哈希條目呢?

解決方案不是釋放d而是通過分配更多struct dictEntry並將它們分配給適當的next來擴展d

當收縮d你將不得不遍歷next到達終點,然后開始釋放的內存struct dictEntry是你的內部d

為了澄清格雷厄姆的觀點,你需要注意在這個庫中如何訪問內存。 用戶有一個指向其字典的指針。 重新散列時,釋放該指針引用的內存。 雖然您為它們分配了一個新字典,但新指針永遠不會返回給它們,因此它們不知道不使用舊字典。 當他們再次嘗試訪問他們的字典時,它指向釋放的內存。

一種可能性是不要完全丟棄舊字典,而只刪除在字典中分配的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) ,我想你可能意味着%模量。

你正在釋放一個傳入你的函數的指針。 只有當你知道調用你的函數的人仍然沒有嘗試使用舊的d值時,這才是安全的。 檢查所有調用_dictRehash()的代碼,確保沒有任何內容掛在舊指針上。

dictCreate實際上做了什么?

我認為你在(固定大小) dict對象和dictEntriesdict.table指針(可能是可變大小的)數組之間dictEntries dict.table

也許你可以只是realloc() dict.table指向的內存,而不是創建一個新的'dict'對象並釋放舊的對象(順便說一句,無論如何都不會釋放指責表!)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM