1

I know this is a question that has been asked quite frequently but I have read 10+ closed questions without any luck as my solution seems to match those proposed as solutions by others.

I am writing my own shell as a learning exercise and in doing so, I am malloc'ing an array of strings to serve as the arguments of a program call. My malloc is done as follows where argcnt is the number of arguments given + 2 to match the standard argv size and allow the array to be null terminated and thus used by execvp:

char ** args;
args = (char **) malloc(argcnt*sizeof(char *));
for(i = 0; i < argcnt; i++) {
    args[i] = (char *) malloc(80*sizeof(char));
    printf("Made %d\n", i);
}

I then free the same memory as follows:

for (i = 0; i < argcnt; i++) {
    free(args[i]);
    printf("Freed %d\n", i);
}
free(args);

The program compiles but fails at freeing argv[1] at runtime. The sample output of running the program and then calling ls -a -l is as follows:

jack@ubuntu:~/myshell$ ./myshell 
[30/03 23:34] # ls -a -l
Argcnt: 4
Made 0
Made 1
Made 2
Made 3
Arg[0]: ls
Arg[1]: -a
Arg[2]: -l
Arg[3]: (null)
Freed 0
*** Error in `./myshell': free(): invalid pointer: 0x0000000001a84c43 ***
Aborted (core dumped)

I have been wrestling with this for the last 2 hours and can't work out what is wrong so some insight into the problem would be very much appreciated.

EDIT: The function currently breaking my program is:

void breakargs(char * cmd, char ** args) {
    char * tok = malloc(strlen(cmd)*sizeof(char)); //maximum token size is full cmd
    char * str = malloc(strlen(cmd)*sizeof(char));
    int i=1;
    strcpy(str, cmd); //maintains integrity of cmd

    args[0] = tok = strtok(str, " ");
    while (tok != NULL)
    {
        args[i] = tok = strtok(NULL, " ");
        i++;
    }
    args[i] = '\0';
    free(tok);
}

2nd EDIT: The problem was my reassigning of the pointers made by my original malloc with strtok so that the original pointer reference was lost. Furthermore, my use of a definite string length for arguments could potentially lead to problems. The solution was to only malloc args[i] when I knew the length of the string that needs to be stored there, and then move the string into that memory location using strcpy instead of direct assignment as I had been doing. This maintains the integrity of the pointer reference and allows it to be freed correctly.

Jackobyte
  • 23
  • 4
  • 2
    Try *just* allocating and then freeing, with nothing in between. Is that successful? – Jonathon Reinhart Mar 30 '16 at 22:41
  • 2
    Something like `char **` is **not** a 2D array and cannot represent one. An array is a continuous memory block of elements of the same type with a linear addressing scheme. For a 2D array each element is an array itself. You have a "pointer to pointer". If you want a 2D array, use one. A typical declaration would be `char (*arr)[INNER_LENGTH];` note the parenthesis! Only this can be allocated and freed with a single `malloc`/`free`. – too honest for this site Mar 30 '16 at 22:41
  • [Don't cast the result of malloc in C](http://stackoverflow.com/q/605845/3959454) – Anton Savin Mar 30 '16 at 22:43
  • @Olaf He's using the wrong terminology, yes, but if you look at his code, he understands that he's allocating an array of pointers to allocated memory, both at allocation-time and free-time. Nowhere does he actually try to *use* it as a single contiguous block of memory, or try to use a single `malloc` / `free`. – Jonathon Reinhart Mar 30 '16 at 22:45
  • The '80' magic number does not fill me with confidence. If the OP copies in 80 chars as a test, together with a null terminator, that's 81:( – Martin James Mar 30 '16 at 22:47
  • Your `malloc()`s appear to match up correctly with your `free()`s, but they also look a bit fishy. If, as you say, `argcnt` is sufficient to hold pointers to the program name, all the arguments, and a terminating `NULL`, then *why are you allocating space for the element intended to hold `NULL`*? Indeed, why are you allocating fixed-size blocks for the array arguments at all? – John Bollinger Mar 30 '16 at 22:49
  • @JonathonReinhart: Another day another discussion why using the wrong terms is not much of a problem. This is read by beginners, who take the terms literally and use them for the wrong things. I tried to avoid this by explaining what an array is and why this is not. I did not say OP should use a 2D array, just he should not use the term for something it is not. If I was talking about a chair, while meaning a car, you would also be confused. Correct usage of term is important to avoid confusion **.** Note that here OP really **should** use a 2D array - single `malloc`, single `free`. – too honest for this site Mar 30 '16 at 22:49
  • Forget the `malloc(80)` and use `strdup()` instead. – Jonathon Reinhart Mar 30 '16 at 22:49
  • I see no results of the expected debugging steps, eg. what @JonathonReinhart suggests - remove all the gunge in between to try and get to a point that does not crash. – Martin James Mar 30 '16 at 22:50
  • @Olaf Trust me, I am a pedantic dick when it comes to terminology, and fully understand its importance. Your comment reads like *"you're creating a 2D array wrong; do it this way"*. Instead, you should have looked at the code and realized that he didn't *want* a 2D array, and instead commented *"Hey, this thing you're creating isn't a 2D array - it's an array of pointers to allocations. You should call it that."* -- I apologize if I misinterpreted your comment, I just didn't want to further confuse this guy. – Jonathon Reinhart Mar 30 '16 at 22:52
  • @JonathonReinhart, if I just allocate and free, it terminated successfully. What is happening between is the assignment to args[0] to args[n] using strtok to break up the `ls -a -l` block. – Jackobyte Mar 30 '16 at 22:52
  • @Jackobyte Ok. Time to track down the memory corruption in the *rest* of your code, which you haven't shown here. Like we've said - the 80 is suspicious; I suspect the code writing to it is as well. I would re-write it to not allocate a fixed 80 bytes. Use `strdup` if you must copy the strings to newly allocated memory. Otherwise, fire up `valgrind` to track down the corruption. – Jonathon Reinhart Mar 30 '16 at 22:54
  • That's as I suppose most of us expected. Chances are that you are double-freeing a pointer, or that you are corrupting the memory manager's data structures by overrunning the bounds of one of your objects. – John Bollinger Mar 30 '16 at 22:54
  • @MartinJames, I just used 80 as it seemed like a reasonable length to allocate an argument as. I'm trying to emulate a bash-like shell and personally haven't had much use for an argument longer than 80 characters. – Jackobyte Mar 30 '16 at 22:56
  • @JonathonReinhart: Sorry, I just had this discussion in another thread, not to mention the many other days. So, what's wrong with that? I just tell him this is no 2D array, so he should not call it like that. Just because there is no nice, short name for this kind of construct does not justify calling it 2D array. Maybe we should start a naming contest for this recurring (and very well usable) construct? What he actually should use depends on the rest of the code ([mcve]?). – too honest for this site Mar 30 '16 at 22:58
  • "personally haven't had much use for an argument longer than 80 characters" - You might be surprised what the shell-scripts your system uses under the hood might pass as arguments. If that's what you are up to, you need quite a different approach. – too honest for this site Mar 30 '16 at 23:01
  • 2
    You said "assignment to args[0] to args[n] using strtok", are you just assigning the return value of `strtok` to your `arg[i]`? If so, you are throwing away the allocated pointer and replacing it with a pointer into the original string. You will then have a problem with `free` as you haven't `malloc`ed those pointers. Use `strcpy` instead. This is why it is important to show the right amount of code in your posts. – The Dark Mar 30 '16 at 23:07
  • I have traced my problem to a function called between the allocation and freeing of the memory. The function `breakargs(cmd, args);` takes the complete line given in by the user and breaks it up using strtok, populating args with each component. I will attach breakargs function to my original post. – Jackobyte Mar 30 '16 at 23:07
  • @Jackobyte Post that code. What you have now is not an [mcve]. – Jonathon Reinhart Mar 30 '16 at 23:08
  • Re edit: you allocate memory for `tok` then overwrite it with the result of `strtok`. You then try to `free(tok)` but it was not the pointer returned by `malloc` – Weather Vane Mar 30 '16 at 23:20
  • @The Dark has spotted the right answer : just use `strcpy(args[0], tok)` instead of `args[0] = tok`. The latter just modifies the pointer, thus creating memory leaks and failure as `free()` is called. Same thing for `args[i] = tok` and `args[i] = '\0';`. Moreover, `'\0'` is a char, not a char*... – francis Mar 30 '16 at 23:22

2 Answers2

1

The problem isn't in the code you show; it is in the code you don't show. Without having read the complete comment chain, I suspect you're doing something similar to this:

 args[0] = "ls";
 args[1] = "-a";
 args[2] = "-l";
 args[3] = NULL;

and then trying to free the args array. Maybe your code was not quite so blatant, but the net result may be much the same. For example, you could have split up a command line that was read into a buffer and might copy the pointers to sections of the buffer into the args array elements. (Update: This seems to be the actual problem.)

This has obliterated the allocated memory (you've lost it — you've leaked memory), and later you're trying to free unallocated memory (pointers that were not returned by malloc() or one of its friends). The first such free() often 'works', but completely messes up the controls used by malloc() et al so that the second free() fails. Many, but not all, modern systems sport a malloc() that detects many such abuses.

You need code more like:

strcpy(args[0], "ls");
strcpy(args[1], "-a");
strcpy(args[2], "-l");
free(args[3]);
args[3] = NULL;

Note that allocating 80 bytes per string was a gross over-allocation for these strings. OTOH, it could also be completely inadequate for other (longer) arguments. You should allocate the memory for args itself as you did; you should probably allocate the memory for the individual elements according to what the corresponding argument contains. There's a (POSIX but not Standard C) function called strdup() that might be what you're looking for — it allocates enough space for a copy of a string and copies the string into the allocated space. Alternatively, you might not need the allocations at all.

If it is available for your platform, you should use valgrind to validate your memory use and identify your abuses.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
1

strtok returns a pointer into the string that was initially passed to strtok, there is no need to allocate space for that pointer.

When you assign the return value of strtok to a pointer variable, you overwrite that variable's pointer value with the new value. This means, if that pointer variable points to memory you have already allocated, that memory will be "leaked" as you no longer have a pointer to it.

In short - if you allocate memory for a pointer variable, don't assign a different value to that pointer variable unless you have already freed the memory or put the pointer value somewhere else.

In your breakArgs function there are a number of issues:

void breakargs(char * cmd, char ** args) {
    char * tok = malloc(strlen(cmd)*sizeof(char)); //maximum token size is full cmd

You don't need to allocate memory for tok as you will assigning it a value from strtok

    char * str = malloc(strlen(cmd)*sizeof(char));
    int i=1;
    strcpy(str, cmd); //maintains integrity of cmd

    args[0] = tok = strtok(str, " ");

Don't assign directly into args[0] as that will overwrite the pointer to the memory you already allocated. Instead assign into tok, then strcpy(args[0], tok);

    while (tok != NULL)
    {
        args[i] = tok = strtok(NULL, " ");

Don't assign directly into args[i] as that will overwrite the pointer to the memory you already allocated. Instead assign into tok, then strcpy(args[i], tok); You should also check for tok being NULL before you strcpy from it.

        i++;
    }
    args[i] = '\0';

Don't assign directly into args[i], instead you could indicate the end by strcpy(args[i], "") so you have a blank string at the end.

    free(tok);

Don't free tok here, as tok should be NULL by this stage, you do need to free (str) though.

}

Also take note of some of the other comments about checking that the arguments you process don't exceed the limit you have put on the memory (e.g. 80 characters per item).

The Dark
  • 8,453
  • 1
  • 16
  • 19
  • These steps have fixed nearly all my problems. The only one remaining is that I need the last value in args to be NULL as execvp looks for a NULL terminator in the accepted arguments. Strcpy'ing over an empty string causes the `ls` command to look for a file named "" so is an insufficient solution. If I assign it manually as I have above, `args[i]='\0'`, the program seems to work, including the successful freeing of args[3]. Is it silently failing behind the scenes though? – Jackobyte Mar 30 '16 at 23:45
  • 1
    In this case `'\0'` is being treated as `0` which is the same as `NULL`. What is happening is that you are leaking the memory that was allocated for the last `argv[i]` as you have lost what the pointer was. To fix this, you could `free(argv[i])` before saying `argv[i]=NULL` (use NULL as it is clearer then '\0'). That would free the memory, but it would leave you with the odd situation where the caller has to allocate memory for the array elements that may or may no be freed. It would be better to change your `breakArgs` function to do the `malloc` of the all array elements itself. – The Dark Mar 31 '16 at 00:00
  • As some of the other commenters have said - you could use strdup to allocate and copy the string from `tok`, then you wouldn't need to pre-allocate the arr entries. – The Dark Mar 31 '16 at 00:01
  • I have now moved where I allocate args[i] into the breakArgs function so that the size of each matches the length of the relevant argument, `args[i] = malloc(strlen(tok) * sizeof(char));` before then using strcpy to move the argument into this memory location. In this way, the first definition of the last arg[i] is in the line `args[i]=NULL;`, and I don't need to mess with freeing it before assigning to it. – Jackobyte Mar 31 '16 at 00:12
  • Should be `malloc((strlen(tok)+1) * sizeof(char));` - don't forget those null terminator bytes at the end of strings. Alternatively `args[i] = strdup(tok)` would be simpler, then you don't need the strcpy. – The Dark Mar 31 '16 at 00:18