简体   繁体   中英

C: malloc of string showing unexpected behaviour with double pointer and function calls

This is a query to understand how the below code is working fine even with a mistake.

As per my knowledge, if I want to reallocate/ re-assign a pointer passed to a function, that pointer needs to be passed as a double pointer. By mistake, I passed a single pointer and the program is still working. I guess it has to do something with the pointer being a string.

Program:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void func2( char **x){

    printf("befor func2 x = %u; *x = %u; **x = %s; x_size = %u\n", x, *x, *x, strlen(*x));
    free(*x);
    *x = (char *)malloc(20);
    strcpy(*x, "zyxwvutsrqponmlkjih");

    printf("\n\nafter func2 x = %u; *x = %u; **x = %s; x_size = %u\n", x, *x, *x, strlen(*x));
}

void func1( char *x){

    printf("befor func1 &x = %u; x = %u; *x = %s; x_size = %u \n", &x, x, x, strlen(x));
    func2(&x);
    printf("after func1 &x = %u; x = %u; *x = %s; x_size = %u \n", &x, x, x, strlen(x));
}

int main(){

    char *x; 
    x = (char *)malloc(10);
    strcpy(x, "abcdefghi");
    printf("befor  main &x = %u; x = %u; x = %s; x_size = %u\n", &x, x, x, strlen(x));
    func1(x);
    printf("after  main &x = %u; x = %u; x = %s; x_size = %u\n", &x, x, x, strlen(x));
    free(x);
    return 1;
}

OutPut:

befor  main &x = 489275896; x = 20414480; x = abcdefghi; x_size = 9
befor func1 &x = 489275864; x = 20414480; *x = abcdefghi; x_size = 9 
befor func2 x = 489275864; *x = 20414480; **x = abcdefghi; x_size = 9


after func2 x = 489275864; *x = 20414480; **x = zyxwvutsrqponmlkjih; x_size = 19
after func1 &x = 489275864; x = 20414480; *x = zyxwvutsrqponmlkjih; x_size = 19 
after  main &x = 489275896; x = 20414480; x = zyxwvutsrqponmlkjih; x_size = 19

I can understand the output till func1 . But how the size and values are getting returned to main after being modified in func2 ? I've not passed x as a double pointer from main to func1 . But somehow it's still working.
Is it because it is a char * ?

Edit 1:

After suggested edits in the comments:

Program:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void func2( char **x){

    printf("befor func2 x = %p; *x = %p; **x = %s; x_size = %u\n", x, *x, *x, strlen(*x));
    free(*x);
    *x = (char *)malloc(20);
    strcpy(*x, "zyxwvutsrqponmlkjih");

    printf("\n\nafter func2 x = %p; *x = %p; **x = %s; x_size = %u\n", x, *x, *x, strlen(*x));
}

void func1( char *x){

    printf("befor func1 &x = %p; x = %p; *x = %s; x_size = %u \n", &x, x, x, strlen(x));
    func2(&x);
    printf("after func1 &x = %p; x = %p; *x = %s; x_size = %u \n", &x, x, x, strlen(x));
}

int main(){

    char *x, *y, *z; 
    x = (char *)malloc(10);
    z = (char *)malloc(100);
    y = (char *)malloc(100);
    strcpy(x, "abcdefghi");
    printf("befor  main &x = %p; x = %p; x = %s; x_size = %u\n", &x, x, x, strlen(x));
    func1(x);
    printf("after  main &x = %p; x = %p; x = %s; x_size = %u\n", &x, x, x, strlen(x));
    free(x);
    free(y);
    free(z);
    return 1;
}

Output:

befor  main &x = 0x7fff78cb09c8; x = 0x1c7a010; x = abcdefghi; x_size = 9
befor func1 &x = 0x7fff78cb09a8; x = 0x1c7a010; *x = abcdefghi; x_size = 9 
befor func2 x = 0x7fff78cb09a8; *x = 0x1c7a010; **x = abcdefghi; x_size = 9


after func2 x = 0x7fff78cb09a8; *x = 0x1c7a010; **x = zyxwvutsrqponmlkjih; x_size = 19
after func1 &x = 0x7fff78cb09a8; x = 0x1c7a010; *x = zyxwvutsrqponmlkjih; x_size = 19 
after  main &x = 0x7fff78cb09c8; x = 0x1c7a010; x = zyxwvutsrqponmlkjih; x_size = 19

The program still works after introducing multiple mallocs.

What you have is undefined behavior .

Here follows the important parts of your program (with renamed variables to be able to differ between them in the functions):

void func2(char **x)
{
    free(*x);
    *x = malloc(SOME_OTHER_SIZE);
}

void func1(char *y)
{
    func2(&y);
}

int main(void)
{
    char *z = malloc(SOME_SIZE);
    func1(z);
}

In the main function you allocate some memory, and make z point to it.

Then you call func1 passing the pointer z by value , meaning that the pointer is copied into the func1 argument variable y . Now you have two pointers pointing to the same memory: z in the main function and y in the func1 function.

Then func1 calls func2 , but it emulates pass by reference by passing not a copy of the value in y but a pointer to the variable y itself. When func2 free's the memory pointed to by *x , it invalidates the pointers *x , y and z . It then reassigns *x to point to some new memory. This will change where y is pointing but not z , which will still be invalid.

When func1 returns the pointer z is no longer valid, any attempt to dereference it will lead to said undefined behavior .


Somewhat graphically it could be seen like this:

  1. The main function allocates memory and makes z point to it:

     +---+ +-----------+ | z | --> | Memory... | +---+ +-----------+
  2. The function func1 is called, passing a copy of z :

     +---+ | z | -\ +---+ \ +-----------+ >-> | Memory... | +---+ / +-----------+ | y | -/ +---+
  3. The function func2 is called, passing a pointer to y :

     +---+ | z | -\ +---+ \ +-----------+ >-> | Memory... | +---+ +---+ / +-----------+ | x | --> | y | -/ +---+ +---+
  4. The function func2 free's the memory pointed to by *x :

     +---+ | z | -\ +---+ \ >->??? +---+ +---+ / | x | --> | y | -/ +---+ +---+
  5. The function func2 allocates new memory and makes *x (and therefore y ) point to it:

     +---+ | z | -->??? +---+ +---+ +---+ +---------------+ | x | --> | y | --> | New memory... | +---+ +---+ +---------------+

From the above it should hopefully be easy to see why free(*x) in func2 will invalidate z from the main function as well.

Now the interesting part, which is why the memory pointed to by z in the main function seems to be changed: It's seems to be a quirk of the memory allocator in your system, where it maps the new allocation to the same location as the old allocation. The important point is that z is still invalid.

You seem to think that using char *x (a single pointer) as an argument for func1 was a mistake. However, I think it's perfectly fine. func1 is expecting a pointer to char as an argument, or basically a memory address that when dereferenced gives a char or bunch of chars. When you write func1(x); in main, you are passing x, the address of a bunch of chars, which is just the type of argument func1 was expecting.

Why is x the address of a bunch of chars? In this case, the pointer x is storing the address of an array (of characters). Now, you might know that if you just write the name of an array, you get the base address of the array. See the below code:

#include <stdio.h>
#include <stdlib.h>

int main () {
    int arr[5] = {1, 2, 3, 4, 5};
    printf ("%d\n", arr);       // address of the first element (1) of the array
                                // or, the base address of the array
    printf ("%d\n", &arr[0]);   // same as above
    printf ("%d\n\n", *arr);      // gives the first element of the array

    int *x = malloc (5*sizeof (int));
    *x = 1;
    *(x+1) = 2;
    *(x+2) = 3;
    *(x+3) = 4;
    *(x+4) = 5;
    printf ("%d\n", x);        // address of the first element (1) of the array
                               // or, the base address of the array
    printf ("%d\n", &*(x+0));  // same as above
    printf ("%d\n\n", *x);     // gives the first element of the array
    return 0;
}

The output is as follows:

6356712
6356712
1

9768168
9768168
1

Now, why are we able to modify the values in the character array pointed to by x? Because we have passed the base address of that array. We can dereference that address to get to the array and do whatever we want with it.

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