Confused by single pointer and double pointer arguments in function calls
Asked Answered
H

3

6

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
Haversack answered 3/8, 2014 at 17:8 Comment(10)
You can always modify what a pointer refers to. However, as all things in C are passed by value (copy), you cannot change the function argument such that the caller can see the change unless you add a level of indirection. i.e., this code modifies only the local copy; void foo(int *p) { p = malloc(size); }.Mandate
if use pointer, then pass by pointer, not by value. If use double pointer, pass address of pointer. If want to pass value, pass without any pointers.Begley
@IvanIvanov: I don't mean to be pedantic, but as this is an issue which seems to confuse all beginners, everything in C is passed by value. That includes pointers as well.Mandate
Yes, this is confusing, because when you pass value of something that is not a pointer it is called pass-by-value, and when you pass value of pointer, this is called pass-by-pointer.Begley
@IvanIvanov: I've never used the term "pass-by-pointer". I think it's more simple to just understand that, in C, everything is passed by value. Pointers are not special in any way.Mandate
@EdS. Hi Ed, are you saying that having a double pointer as an argument allows us to change the address to what it is currently pointing to? I guess I am not clear on what "indirection" means... :-\Haversack
I've found this terms in different books. I think that in every language where stack is used everything is passed by value. For example, in Java there are pass-by-value and pass-by-reference, while copy of value or of address is pushed on the stack, and this terminology is widely spread...Begley
@urbanspr1nter: It allows you to change what the pointer pooints to, yes. In this case, it points to another pointer, so you can modify it's value (i.e., an address) and the caller will see the change because you are modifying that memory directly.Mandate
@IvanIvanov: I'm not sure what it has to do with the stack, which is an implementation detail of how locals are stored. The spec doesn't even mention a "stack", only variable lifetime. Java also has a stack. C# has a stack. That has nothing to do with argument passing semanticsMandate
javadude.com/articles/passbyvalue.htmBegley
B
23

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).

Begley answered 3/8, 2014 at 17:23 Comment(3)
Hi Ivan, same thing with my comment to Ed above, are you saying that a "modify" is essentially changing where the structure is pointing to in memory, while we are not allowed to do this with a single pointer?Haversack
Very Nice Explanation.Drip
Excellent, just the summary I needed !Hovis
H
5

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.

Hogback answered 3/8, 2014 at 17:13 Comment(0)
K
1

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().

Kreiker answered 3/8, 2014 at 17:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.