Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

malloc and free in recursive functions

I have some code for you and hope someone can tell me, what I'm getting wrong. Currently I'm porting my programming puzzles to other programming languages, to just get some hands on.

The code abstraction in C (updated):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
const char *dummy = "1234567890";
const char* inlet = "_";

void dosomething(int c, char* s){
  printf("%s\n", s);
  if (c < 10) {
    char *ns = malloc(sizeof(char)*11);
    strncpy(ns, s, c-1);
    strncat(ns, inlet, 1);
    strcat(ns, &s[c]);
    dosomething(c+1, ns);
    //free(ns);
  }
}

void main() {
  for(int i = 0; i < 100; i++) {
    char *s = malloc(sizeof(char)*11);
    strcpy(s, dummy);
    dosomething(1, s);
    free(s);
  }
}

The code runs just fine, until I uncomment the free() call in the dosomething() method. And that is what I don't understand right. As I see it, there is absolutely no problem in freeing the memory, as it is no longer used after returning from the recursive call, but programm output tells something different.

Output without free is as expected:

...
1_34567890
1_34567890
...

With the second free, only one result is made, than the program stops with :

*** Error in `./a.out': malloc(): memory corruption (fast): 0x000000000164e0d0 ***
Abgebrochen (Speicherabzug geschrieben)

Update: I changed the code according to the comments and answers, but the problem still exists. Allocating more memory with malloc does not prevent the memory error, if the free() call inside the dosomething() method is incommented. Output is generated correctly for the first iteration of the recursion, the second shows different results, the third as well and then the program fails (see the new printf on top of the function for the new results:

Output:

1234567890
_234567890
__34567890
___4567890
____567890
_____67890
______7890
_______890
________90
_________0
1234567890
@@J_234567890
@@J_J_234567890
@@J__J_234567890
@@J___J_234567890
@@J___J_234567890
@@J___J_234567890
@@J____J_234567890
@@J____J_234567890
@@J_____0__234567890
1234567890
@@J_234567890
@@J_J_234567890
@@J__J_234567890
@@J___J_234567890
@@J___J_234567890
@@J___J_234567890
@@J____J_234567890
@@J____J_234567890
@@J_____0__234567890__234567890
*** Error in `./a.out': free(): invalid next size (fast): 0x00000000014a4130 ***
Abgebrochen (Speicherabzug geschrieben)

Can anyone please explain to me, what I'm blinking at?

Update2: @Michi and @MichaelWalz have worked out the point. It is a combination between using malloc - thus working with garbage in the memory after the first iteration (printing the memory adresses along with the strings shows that pretty neat), and using strcat on that.

Using strcat on not initialized memory will append the string in memory to the next "\0" character found after the pointer in memory. If the memory is not initialized, this can be far out of bounds of that string.

Thank you guys!

Working code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
const char *dummy = "1234567890";
const char* inlet = "_";

void dosomething(int c, char* s){
  printf("%p %s\n", s, s);
  if (c < 10) {
    //char *ns = malloc(sizeof(char)*11);
    char *ns = calloc(11, sizeof(char));
    strncpy(ns, s, c);
    strncat(ns, inlet, 1);
    strncat(ns, &s[c+1],10-c);
    dosomething(c+1, ns);
    free(ns);
  }
}

void main() {
  for(int i = 0; i < 100; i++) {
    //char *s = malloc(sizeof(char)*11);
    char *s = calloc(11, sizeof(char));
    strcpy(s, dummy);
    dosomething(0, s);
    free(s);
  }
}
like image 614
Oliver Friedrich Avatar asked Nov 29 '22 14:11

Oliver Friedrich


1 Answers

The reason is because the malloc function allocates 10 characters while 11 are needed (ending \0).

While this is implementation dependent, it is likely the malloc function, to be efficient, uses some bytes within and besides the allocated area to set some internal information. After this internal area has been altered (one char too much), free may use these bytes and the final result is undefined behavior.

Anyway, altering or even reading an array out of bounds is UB.

Better use

char *s = malloc(strlen(dummy) + 1);

and don't cast the resulting pointer of malloc.

like image 163
Déjà vu Avatar answered Dec 09 '22 14:12

Déjà vu