Search code examples
cpointersstructsegmentation-faultfree

Why does free() of a struct result in segfault (wrong usage of pointers)?


When I try to free my struct, the program crashes because of a segfault. Inspecting the program with valgrind I have found:

==9761== Invalid free() / delete / delete[] / realloc()
==9761==    at 0x484827F: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==9761==    by 0x109242: destroyHashTable (hashtable.c:38)
==9761==    by 0x10942E: main (hashtable_main.c:17)
==9761==  Address 0x1ffefffa70 is on thread 1's stack
==9761==  in frame #2, created by main (hashtable_main.c:7)

I cannot really say anything more useful than having no idea, how to solve it. The crash happens during the free(ht) in destroyHashTable(ht) in hashtable.c. What am I doing wrong?

Below the code hashTable_main.c:

#include <stdio.h>
#include <stdlib.h>


#include "hashtable.h"

int main() {

    hashTable* ht = NULL;

    initHashTable(&ht);

    int totalColCount = 0;

    totalColCount += addHashTableEntry(&ht, "PRPR2");

    destroyHashTable(&ht);

    return EXIT_SUCCESS;
}

hashtable.c:

#include <stdlib.h>
#include <stdio.h>


#include "hashtable.h"

/* private internal API */
int hash_funktion(char *string);
hashtableEntry* createTableEntry(char* newKey) ;
/* end of private internal API */


int hash_funktion(char *string) {
    unsigned int hash_adresse;
    unsigned char *pointer;
    hash_adresse = 0;
    pointer = (unsigned char *) string;
    while(*pointer != '\0') {
        hash_adresse = 19 * hash_adresse + *pointer;
        pointer++;
    }
    return hash_adresse % MAX_HASH;
}

hashtableEntry* createTableEntry(char* newKey) {
     hashtableEntry* e = (hashtableEntry*) malloc (sizeof(hashtableEntry));
     e->hashKey = newKey;
     return e;
}

void initHashTable(hashTable* ht) {
    ht = (hashTable*) malloc (sizeof (struct hashTable));
    ht->table = (hashtableEntry*) malloc (MAX_HASH * sizeof (hashtableEntry));
}

void destroyHashTable(hashTable* ht) {
    if (ht) {
        free(ht);
        ht = NULL;
    }
}

int  addHashTableEntry(hashtableEntry* ht, char* keyValue) {
    hashtableEntry *e = createTableEntry(keyValue);

    int colCounter = 0;

    int hashValue = hash_funktion(keyValue);

    if (ht[hashValue].hashKey == NULL) {
        ht[hashValue] = *e;
        return 0;
    } else {
        int newVal = (hashValue + 1) % MAX_HASH;
        colCounter++;
        while (ht[newVal].hashKey != NULL && newVal != hashValue ) {
            newVal = (newVal + 1) % MAX_HASH;
            colCounter++;
        }
        if (newVal != hashValue) {
            ht[newVal] = *e;  
            return colCounter;      
        } else {
            return -1;
        }
    }
}

bool searchValue(hashtableEntry* ht, char* searchValue) {    
    for (int i = 0; i < MAX_HASH; i++)
    {
        if(ht[i].hashKey == searchValue) {
            return true;
        }
    }
    return false;
}

and hashtable.h:

#pragma once

#define MAX_HASH 20
#include <stdbool.h>

typedef struct hashtableEntry {
    char* hashKey;
} hashtableEntry;

typedef struct hashTable {
    hashtableEntry* table;
    int elemCount;
} hashTable;

void initHashTable(hashTable* ht);

void destroyHashTable(hashTable* ht);

int  addHashTableEntry(hashtableEntry* ht, char* keyValue);

bool searchValue(hashtableEntry* ht, char* searchValue);

Solution

  • There never was a hashtable to begin with. The issue lies in initHashTable. It should be accepting a double pointer since it is given a pointer to a pointer it should initialize. The reason it can segfault despite the check in destroyHashTable is that the pointer is left uninitialized and may be non-zero at the start of program execution.

    void initHashTable(hashTable** ht) {
        *ht = (hashTable*) malloc (sizeof (struct hashTable));
        (*ht)->table = (hashtableEntry*) malloc (MAX_HASH * sizeof (hashtableEntry));
    }
    

    You may find it easier to instead return the newly created hash table. This better expresses that initHashTable is giving you a new hashTable * value.

    hashTable *initHashTable() {
        hashTable *ht = (hashTable *) malloc (sizeof (struct hashTable));
        ht.table = (hashtableEntry *) malloc (MAX_HASH * sizeof (hashtableEntry));
        return ht;
    }
    

    There are also a bunch of other places where pointers are not handled correctly.

    void doThing(Foo *foo) {
        // This changes foo, but not the data foo points to.
        foo = something;
        // This changes the data foo points to
        *foo = someOtherThing;
    }
    
    void doStuff() {
        Foo *foo;
    
        // This is incorrect since it creates a double pointer. doThing would need to
        // be defined as "void doThing(Foo **foo)" to be correct.
        doThing(&foo);
    
        // Instead we can just pass the existing pointer
        doThing(foo);
    
    
        // We only need to create a reference if the value does not start out as a pointer
        Foo bar;
        doThing(&bar);
    }