Confuses single-pointer and double-pointer pointers in function calls

I'm trying to get a deeper understanding of pointer arguments in functions for C. I wrote a test program to try and see the difference between passing a single pointer and a double pointer to a function, and then modifying it.

I have a program that has two functions. The first function modifyMe1

takes one pointer as an argument and changes property a to 7. The second function modifyMe2

takes a double pointer as an argument and changes property a to 7.

I expected the first function to modifyMe1

be "pass-by-value", that is, if I passed my pointer to a struct, C would create a copy of the data it specified. Whereas with the latter, I am making a "pass-through" that should modify the structure in place.

However, when I test this program, both functions seem to change the structure in place. I know that for me the misunderstanding for the nature of pointers is the arguments. Can someone help me clean this up?

Thank!

Here's what I have:

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

struct myStructure {
    int a;
    int b;
};

void modifyMe1(struct myStructure *param1) {
    param1->a = 7;
}

void modifyMe2(struct myStructure **param1) {
    (*param1)->a = 7;
}

int main(int argc, char *argv[]) {
    struct myStructure *test1;

    test1 = malloc(sizeof(test1));
    test1->a = 5;
    test1->b = 6;

    modifyMe1(test1);

    printf("a: %d, b: %d\n", test1->a, test1->b);

    // set it back to 5
    test1->a = 5;
    printf("reset. a: %d, b: %d\n", test1->a, test1->b);

    modifyMe2(&test1);

    printf("a: %d, b: %d\n", test1->a, test1->b);


    free(test1);
    return 0;
}

      

In which my output is:

$ ./a
a: 7, b: 6
reset. a: 5, b: 6
a: 7, b: 6

      

+3


source to share


3 answers


You can pass an argument in different ways in C (Captain Obvious, yes).



  • By value. Then it is copied onto the stack. Thus, the function has a local copy of the variable in the function frame. Any changes to the argument do not change the passed value. This is similar to read-only mode

    void fooByValue(myStructure_t arg) {
        printf("passed by value %d %d\n", arg.a, arg.b);
        arg.a = 0;
    }
    
          

  • Follow the signpost. Then the address of that variable is copied (so yes, it is still passed by value, but you are passing the value of the address, not the integer argument). Thus, it is similar to read / write mode. Since you can access the passed variable through your address, you can change the value of that variable outside of the function.

    void fooByPtr(myStructure_t *arg) {
        printf("passed by pointer %d %d\n", arg->a, arg->b);
        arg->a = 0;
    }
    
          

    But! You still cannot change the pointer.

  • So, if you want to change a pointer, you should pass a pointer to pointer. This is similar to read-write mode:

    void fooByDblPtr(myStructure_t **arg) {
        *arg = (myStructure_t*) malloc(sizeof(myStructure_t));
        (*arg)->a = 10;
        (*arg)->b = 20;
    }
    
          

    If it was just a pointer, then there was a memory leak:

    void fooByDblPtr(myStructure_t *arg) {
        arg = (myStructure_t*) malloc(sizeof(myStructure_t));
        (arg)->a = 10;
        (arg)->b = 20;
    }
    
          

    because here you are assigning a new address to the local copy of the address, and that argument will be destroyed after the function completes.

    UPD. For example, we have

    void fooByPtr(myStructure_t *arg) {
        printf("addr inside foo before %p\n", arg);
        arg = (myStructure_t*) malloc(sizeof(myStructure_t));
        (arg)->a = 10;
        (arg)->b = 20;
        printf("addr inside foo after %p\n", arg);
    }
    
    void main() {
        myStructure_t *x = NULL;
        x = malloc(sizeof(myStructure_t));
        x->a = 10;
        x->b = 20;
        printf("x addr before = %p\n", x);
        fooByPtr(x);
        printf("x addr after = %p\n", x);
        free(x);
    }
    
          

    internal function memory is allocated, and the pointer is assigned to a local variable. Caller still retains the old value. After calling the function, we lose the memory address so that it cannot be freed.

    Short conclusion: there is a simple rule - if you need to change an argument, pass a pointer to it. So, if you want to change the pointer, go pointer to pointer. If you want to change the double pointer, pass pointer to pointer to pointer.

  • Passing an argument by a pointer is also much faster because you don't have to copy all the values ​​on the stack (of course, if the value is greater than the pointer to that value, otherwise passing by a read-only pointer doesn't make sense). But this is dangerous because it can be changed inside the function. This way you can protect the argument defining it with the const keyword

    void constFoo(const myStructure_t *arg) {
        arg->a = 10;    //compilation error
        arg->b = 20;    //compilation error
    }
    
          

    This is really useful when working with third party libraries: a function signature tells you if a function can change your argument or not. Although const is optional, it makes sense to write the const keyword whenever possible.

  • Passing an array. Typically, the size of the array (hence size_t) is also sent as an argument. You are passing the array as a pointer.

    void foo (int *buf, size_t nbuf) {
        ....
    }
    
          

    Sometimes you can find code where the developer sends a pointer to an object instead of an array, like

    void foo (int *buf, size_t size) {
        size_t i;
        for (i = 0; i < size; i++) {
            printf("%d ", buf[i]);
        }
    }
    
    int main(int argc, char **argv) {
        int a = 10;
        int buf[1] = { 10 };
        foo(buf, 1);
        foo(&a, 1);
    }
    
          

    In this case, an array of one element and a pointer to an element behave the same (although they are not the same).

+17


source


with a regular parameter, say int

you get a local copy
with a pointer parameter, say int*

you can change what it points to
with a double pointer parameter, say int**

you can change the pointer itself, i.e. "reinstall" it.



+3


source


Add another function:

void modifyMe0(struct myStructure param1)
{
    param1.a = 7;
}

      

This passes the structure by value. Modifications made to the function are not reflected in the argument passed to modifyMe0()

.

Add the calling code like this:

printf("Before 0: a = %d, b = %d\n", test1->a, test1->b);

modifyMe0(*test1);

printf("After  0: a = %d, b = %d\n", test1->a, test1->b);

      

Note that the before and after values ​​are the same in the calling code. You can also add a print to your functions modifyMeN()

to demonstrate that the value changes within them.

When you pass a pointer to the structure of the called function, the value of the structure in the calling function can be changed. When you pass a structure to a called function by value, the value of the structure in the calling function does not change.

You can create another function:

void modifyMe3(struct myStructure **p1)
{
    free(*p1);
    *p1 = malloc(sizeof(*p1));
    (*p1)->a = -3;
    (*p1)->b = -6;
}

      

Add the calling code like this:

printf("Before 3: address = %p, a = %d, b = %d\n", (void *)test1, test1->a, test1->b);

modifyMe0(*test1);

printf("After  3: address = %p, a = %d, b = %d\n", (void *)test1, test1->a, test1->b);

      

Note that the structure address has changed after the call modifyMe3()

.

+1


source







All Articles