Home > Back-end >  Error: free(): double free detected in tcache 2. Calloc/Realloc Array of Structs in C when triggered
Error: free(): double free detected in tcache 2. Calloc/Realloc Array of Structs in C when triggered

Time:12-23

I'm getting back into C after a long break, and as always the problems lie in use of pointers and general memory management.

My specific problem arises from the use of realloc on a struct in order to dynamically grow it as an array as required. I've never really worked with struct arrays before so I do apologise in advance.

Note: I haven't gotten as far as adding any data to the struct data fields as of yet. I have also renamed all variables to generic names for the purpose of this example.

Here's my struct definition:

typedef struct a_struct
{
    char a;
    char b;
    int num;
} TheStruct;

I allocate the first struct via calloc:

TheStruct *new_structInstance(void)
{
    TheStruct *structInstance = NULL;

    structInstance = calloc(1, sizeof(TheStruct));

    return structInstance;
}

This is my attempt at reallocating the first struct into an array of them, which is where I'm almost certainly going wrong:

void add_structInstance_to_array(TheStruct *structInstance, int *numElements, 
int *bufferLen)
{
    if (*numElements == *bufferLen) {
        *bufferLen  = GROWBY; // #define 16
        TheStruct *newstructInstance = realloc(structInstance, 
*bufferLen * sizeof(structInstance));
        if (newstructInstance == NULL) {
            free(structInstance);
            printf("Error: Memory could not be allocated.");
            exit(EXIT_FAILURE);
        }
        else if(structInstance != newstructInstance) {
            structInstance = newstructInstance;
        }
        newstructInstance = NULL;
    }
    *numElements  = 1;
}

I free the array of structs thus:

void free_struct_array(TheStruct *structInstance, int *numElements)
{
    for(int i = *numElements-1; i >= 0; i--) {
        free(&structInstance[i]);
    }
    free(structInstance);
}

Note that the input text file input format read by fgets in main approximately:


char char int

And I drive the whole thing in main:

void main()
{
    TheStruct *structInstance = new_structInstance();
    int initialCreated = 0;

    int bufferLen = 0, numElements = 0;

    char buffer[BUFFER]; // #define 20

    FILE *file = fopen("input.txt", "r");

    while(fgets(buffer, sizeof(buffer), file) != NULL) {
        if(!initialCreated){
            initialCreated = 1;
        }
        else {
            add_structInstance_to_array(structInstance, &numElements, &bufferLen);
        }
    }

    // Free memory
    fclose(file);
    free_struct_array(structInstance, &numElements);
}

Compiling the code generates the following terminal output:

free(): double free detected in tcache 2 Aborted (core dumped)

Valgrind outputs the following (I've skipped more verbose output as it includes my filesystem folder structure):

==102002== LEAK SUMMARY:
==102002==    definitely lost: 128 bytes in 1 blocks
==102002==    indirectly lost: 0 bytes in 0 blocks
==102002==      possibly lost: 0 bytes in 0 blocks
==102002==    still reachable: 472 bytes in 1 blocks
==102002==         suppressed: 0 bytes in 0 blocks
==102002==
==102002== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)

If there is any way I can output more useful information without exposing my file structure, please let me know.

I tried an "easier" version of this example, instead having a single struct and running realloc on an array within the struct, e.g.:

typedef struct a_struct
{
   int *arr;
}

The above worked fine. Modifying the example to use structs as the array elements instead, a bit like a linked list I suppose, is my intention, so that I can access the struct array such as:

a_struct[element].datatype = something;
int getInt = a_struct[element].inttype;

So I suppose the question is, is it possible using my approach above, and if so, where am I going wrong and is there a better approach?

CodePudding user response:

You not only free the individual elements, you also free the first element again.

This code is highly problematic:

void free_struct_array(TheStruct *structInstance, int *numElements)
{
    for(int i = *numElements-1; i >= 0; i--) {
        free(&structInstance[i]);
    }
    free(structInstance);
}

This should be a singular array of structs, it should be one allocation you can just free() in one shot. If it was an array of pointers it'd be TheStruct**.

Remember how you allocated:

structInstance = calloc(1, sizeof(TheStruct));

Your free() calls should pair up exactly with those.

Reminder: Never, ever, call free() on something you did not explicitly allocate1


1 Or that was allocated on your behalf by a malloc() family allocator, as some libraries do.

CodePudding user response:

Aha! Thanks to @tadman, I dug deeper into freeing allocated struct pointers inadvertently through going out of scope / not returning the pointer to the struct.

I fixed the problem by modifying my add_structInstance_to_array to return the pointer to the reallocated struct:

TheStruct *add_structInstance_to_array(TheStruct *structInstance, int *numElements, int *bufferLen)
{
    if (*numElements == *bufferLen) {
        *bufferLen  = GROWBY; // #define 16
        TheStruct *newstructInstance = realloc(structInstance, 
*bufferLen * sizeof(structInstance));
        if (newstructInstance == NULL) {
            free(structInstance);
            printf("Error: Memory could not be allocated.");
            exit(EXIT_FAILURE);
        }
        else if(structInstance != newstructInstance) {
            structInstance = newstructInstance;
        }
        newstructInstance = NULL;
    }
    *numElements  = 1;
    
    return structInstance;
}

To further agree with @tadman, I could also get rid of the free_struct_array function that would loop through individual struct elements, and instead simply free the struct array once in main.

  • Related