Confused by single pointer and double pointer argu

2019-03-10 11:15发布

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

I have a program that has two functions. The first function modifyMe1 takes a single pointer as an argument and changes the a property to 7. The second function modifyMe2 takes a double pointer as an argument and changes the a property to 7.

I expected that the first function modifyMe1, would be "pass-by-value" that is if I passed in my struct pointer, C would create a copy of the data pointed by it. While with the latter, I am doing a "pass-by-reference" which should modify the structure in place.

However, when I test this program out, both functions seem to modify the structure in place. I know there is a misunderstanding for me on the nature of pointers are arguments for sure. Can someone help clear this up for me?

Thanks!

Here is 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条回答
祖国的老花朵
2楼-- · 2019-03-10 11:59

Add another function:

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

This passes the structure by value. The modification made in the function is not reflected in the argument passed to modifyMe0().

Add 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 in the calling code are the same. You could also add printing to your modifyMeN() functions to demonstrate that within them, the value is modified.

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

You could create another function:

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

Add 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 address of the structure has changed after the call to modifyMe3().

查看更多
ら.Afraid
3楼-- · 2019-03-10 12:03

with a regular parameter, say int you get a local copy
with a pointer parameter, say int* you can modify what it points to
with a double pointer parameter, say int** you can modify the pointer itself, ie 'repoint' it.

查看更多
冷血范
4楼-- · 2019-03-10 12:13

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

  1. By value. Then it is copied to stack. So function has local copy of variable in function frame. Any changes to argument do not change passed value. It is like "read only" mode

    void fooByValue(myStructure_t arg) {
        printf("passed by value %d %d\n", arg.a, arg.b);
        arg.a = 0;
    }
    
  2. Pass by pointer. Then copy of address of this variable is passed (so yes, it is still passed by value, but you pass value of address, not of whole argument). So this is like "read and write" mode. As you can access to passed variable through its address, you can change value of this variable outside function.

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

    But! You still can not modify pointer.

  3. So if you want to modify pointer, then you should pass pointer to pointer. That is like "read-write modify" mode:

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

    If that was just pointer then there would be a memory leak:

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

    because here you assign new address to local copy of address, and this argument would be destroyed after function completion.

    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);
    }
    

    inside function memory is allocated and pointer is assigned to local variable. Caller still keeps old value. After function call we lose address of memory so it can not be released.

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

  4. Passing argument by pointer is also much faster, because you don't need to copy all value on the stack (of course, if value is bigger than pointer to this value, otherwise passing by pointer for read-only is pointless). But it is dangerous, because it could be modified inside function. So you can secure argument defining it with const keyword

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

    This is really helpful when you work with 3rd party libraries: function signature tells you whether function can modify your argument or not. Though const is optional it is appropriate to write const keyword every time it is possible

  5. Passing array. Usually, one also sends array size (hence, size_t) as argument. You pass array as pointer.

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

    Sometimes you can find code where developer sends pointer to object instead of array, for example

    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 array of one element and pointer to element behave alike (though, they are not the same).

查看更多
登录 后发表回答