Search code examples
carraysunixmallocfree

Valgrind throws invalid free() when I try to free an array of dynamically allocated pointers


I'm allocating an array of pointers that are dynamically allocated and I try to free them at the end of my program. The problem is that I always get a "invalid free()" error on valgrind, though I really couldn't find what is wrong. For an example: I'm allocating memory using malloc for argv[0] and argv1, and then try to free them in the for loop. I allocate the array of pointers using:

char ucom[10], bin[15] = "/bin/";
char* str = (char *) malloc(MAX_LINE_LENGTH*sizeof(char));
char** argv = (char **) malloc(sizeof(char*)); //ALLOCATING MEMORY FOR ARGV
int status, i = 0;
printf("Shell2$**");
strcpy(bin, "/bin/");
fgets(str, MAX_LINE_LENGTH, stdin); 
char *token;
token = strsep(&str, " ");
while(token != NULL && i < 4){
    if(token[strlen(token)-1] == '\n')
        token[strlen(token)-1] = '\0';
    argv[i] = (char *) malloc(MAX_ARGUMENT_LENGTH*sizeof(char)); //ALLOCATING MEMORY FOR POINTERS INSIDE ARGV, running two times in my example
    printf("\n\nI:%d\n\n",i);
    if(argv[i] == NULL) printf("Memory Allocation Problem");
    argv[i] = token;    
    token = strsep(&str, " ");
    i++;
    argv = (char **)realloc(argv, (i+2)*sizeof(char*));
}

then I try to free them up:

wait(&status);
for(int f = 0; f < i; f++){
    if(argv[f] != NULL)
        free(argv[f]); //Free runs two times as the number of time malloc has been called, but fails at the second free.
}
free(str);
free(argv);

even though the malloc ran 2 times in my example, which allocated memory for argv[0] and argv1, when the for loop at the end tries to free argv1, it fails and valgrind says it's a free invalid, but it succeeded freeing argv[0].

Thank you all in advance!

The output from valgrind: LINK


Solution

  • There is more wrong than just what @FredLarson and I have already pointed out.

    The function strsep() actually modifies the pointer handed to it, so char *token = strsep(&str, " "); changes the pointer. Since str was gotten from malloc, it can't be freed properly.

    If you want to call strsep() on malloc'd memory, you have to save a copy of the original pointer and free it at the end.

    Also, the not strictly portable but highly available function strdup() is really helpful for making a copy of a string, so rather than allocate a bunch of memory and then copy a string to it, you can just strdup() that string (and free it later).

    #define _BSD_SOURCE // for strsep
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    #define MAX_LINE_LENGTH     80
    #define MAX_ARGUMENT_LENGTH 10  // made up numbers
    
    int main()
    {
        char *str = malloc(MAX_LINE_LENGTH);
        char *str_save = str;
        char **argv = malloc(sizeof(char*)); //ALLOCATING MEMORY FOR ARGV
        int i = 0;
    
        printf("Shell2$**"); fflush(stdout); // make sure user sees the prompt
        strcpy(bin, "/bin/");
        fgets(str, MAX_LINE_LENGTH, stdin);
    
        char *token;
    
        while ( (token = strsep(&str, " ")) != NULL  &&  i < 4 )
        {
            if(token[strlen(token)-1] == '\n')
                token[strlen(token)-1] = '\0';
    
            argv[i] = strdup(token);
    
            if(argv[i] == NULL) printf("Memory Allocation Problem");
            i++;
            argv = realloc(argv, i * sizeof(char*));
        }
        argv[i] = NULL; // GOOD IDEA TO ADD THIS
    
        // run the shell
        // wait(&status);
    
        for(int f = 0; f < i; f++){
            if(argv[f] != NULL)
                free(argv[f]);
        }
        free(str_save);
        free(argv);
    }
    

    This does essentially what yours does, but in practice you probably don't even need to allocate the memory for the input line. Why not just define a local buffer? Then you don't have to allocate for the line buffer or the tokens because they all live in linebuffer:

    #define _BSD_SOURCE // for strsep
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    #define MAX_LINE_LENGTH     80
    
    int main()
    {
        char **argv = malloc(sizeof(char*)); //ALLOCATING MEMORY FOR ARGV
        int i = 0;
    
        printf("Shell2$**"); fflush(stdout); // make sure user sees the prompt
    
        char linebuf[MAX_LINE_LENGTH];
        fgets(linebuf, sizeof linebuf, stdin);
    
        char *token;
        char *str = linebuf;
    
        while ( (token = strsep(&str, " ")) != NULL  &&  i < 4 )
        {
            if (token[strlen(token)-1] == '\n')
                token[strlen(token)-1] = '\0';
    
            argv[i++] = token;
    
            argv = realloc(argv, i * sizeof(char*));
        }
        argv[i] = NULL; // GOOD IDEA TO ADD THIS
    
        // run the shell
        // wait(&status);
    
        free(argv);
    }
    

    This removes a whole lot of complications with - as far as I can tell - no less safety.