简体   繁体   中英

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 argv 1 , 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 argv 1 , when the for loop at the end tries to free argv 1 , 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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM