Pointers as function arguments in C

66,186

Solution 1

A reasonable rule of thumb is that you can't exactly change the exact thing that is passed is such a way that the caller sees the change. Passing pointers is the workaround.

Pass By Value: void fcn(int foo)

When passing by value, you get a copy of the value. If you change the value in your function, the caller still sees the original value regardless of your changes.

Pass By Pointer to Value: void fcn(int* foo)

Passing by pointer gives you a copy of the pointer - it points to the same memory location as the original. This memory location is where the original is stored. This lets you change the pointed-to value. However, you can't change the actual pointer to the data since you only received a copy of the pointer.

Pass Pointer to Pointer to Value: void fcn(int** foo)

You get around the above by passing a pointer to a pointer to a value. As above, you can change the value so that the caller will see the change because it's the same memory location as the caller code is using. For the same reason, you can change the pointer to the value. This lets you do such things as allocate memory within the function and return it; &arg2 = calloc(len);. You still can't change the pointer to the pointer, since that's the thing you recieve a copy of.

Solution 2

The difference is simply said in the operations the processor will handle the code with. the value itself is just a adress in both cases, thats true. But as the address gets dereferenced, it's important for the processor and so also for the compiler, to know after dereferencing, what it will be handling with.

Solution 3

If I were to have this code, for example:

int num = 5;
int *ptr = #

What is the difference between the following two functions?:

void func(int **foo);
void func(int *foo);

The first one wants a pointer to a pointer to an int, the second one wants a pointer which directly points to an int.

Where I call the function:

func(&ptr);

As ptr is a pointer to an int, &ptr is an address, compatible with a int **.

The function taking a int * will do somethin different as with int **. The result of the conversation will be completely different, leading to undefined behaviour, maybe causing a crash.

If I pass in func(&ptr) I am effectively passing in a pointer. What difference does it make that the pointer points to another pointer?

               +++++++++++++++++++
adr1 (ptr):    +  adr2           +
               +++++++++++++++++++

               +++++++++++++++++++
adr2 (num):    +  42             +
               +++++++++++++++++++

At adr2, we have an int value, 42.

At adr1, we have the address adr2, having the size of a pointer.

&ptr gives us adr1, ptr, holds the value of &num, which is adr2.

If I use adr1 as an int *, adr2 will be mis-treated as an integer, leading to a (possibly quite big) number.

If I use adr2 as an int **, the first dereference leads to 42, which will be mis-interpreted as an address and possibly make the program crash.

It is more than just optics to have a difference between int * and int **.

I believe the latter will give an incompatibility warning,

... which has a meaning ...

but it seems that the details do not matter so long as you know what you are doing.

Do you?

It seems that perhaps for the sake of readability and understanding the former is a better option (2-star pointer), but from a logical standpoint, what is the difference?

It depends on what the function does with the pointer.

Solution 4

There are two main practical differences:

  1. Passing a pointer to a pointer allows the function to modify the contents of that pointer in a way that the caller can see. A classic example is the second argument to strtol(). Following a call to strtol(), the contents of that pointer should point to the first character in the string that was not parsed to compute the long value. If you just passed the pointer to strtol(), then any changes it made would be local, and it would be impossible to inform the caller what the location was. By passing the address of that pointer, strtol() can modify it in a way that the caller can see. It's just like passing the address of any other variable.

  2. More fundamentally, the compiler needs to know the type that is being pointed to in order to dereference. For instance, when dereferencing a double *, the compiler will interpret (on an implementation where double consumes 8 bytes) the 8 bytes starting at the memory location as the value of the double. But, on a 32 bit implementation, when dereferencing a double **, the compiler will interpret the 4 bytes starting at that location as the address of another double. When dereferencing a pointer, the type being pointed to is the only information the compiler has about how to interpret the data at that address, so knowing the exact type is critical, and this is why it would be an error to think "they're all just pointers, so what's the difference"?

Solution 5

Generally the difference indicates that the function will be assigning to the pointer, and that this assignment should not just be local to the function. For example (and keep in mind these examples are for the purpose of examining the nature of foo and not complete functions, any more than the code in your original post is supposed to be real working code):

void func1 (int *foo) {
    foo = malloc (sizeof (int));
}

int a = 5;
func1 (&a);

Is similar to

void func2 (int foo) {
    foo = 12;
}

int b = 5;
func2 (b);

In the sense that foo may equal 12 in func2(), but when func2() returns, b will still equal 5. In func1(), foo points to a new int, but a is still a when func1() returns.

What if we wanted to change the value of a or b? WRT b, a normal int:

void func3 (int *foo) {
    *foo = 12;
}    

int b = 5;
func2 (&b);

Will work -- notice we needed a pointer to an int. To change the value in a pointer (ie. the address of the int it points to, and not just the value in the int it points to):

void func4 (int **foo) {
    *foo = malloc (sizeof (int));
}

int *a;
foo (&a);

'a' now points to the memory returned by malloc in func4(). The address &a is the address of a, a pointer to an int. An int pointer contains the address of an int. func4() takes the address of an int pointer so that it can put the address of an int into this address, just as func3() takes the address of an int so that it can put a new int value into it.

That's how the different argument styles are used.

Share:
66,186
sherrellbc
Author by

sherrellbc

Updated on July 09, 2022

Comments

  • sherrellbc
    sherrellbc almost 2 years

    If I were to have this code, for example:

    int num = 5;
    int *ptr = #
    

    What is the difference between the following two functions?

    void func(int **foo);
    void func(int *foo); 
    

    Where I call the function:

    func(&ptr); 
    

    I realize that the former of the two takes a pointer to a pointer as a parameter, while the second takes only a pointer.

    If I pass in func(&ptr), I am effectively passing in a pointer. What difference does it make that the pointer points to another pointer?

    I believe the latter will give an incompatibility warning, but it seems that the details do not matter so long as you know what you are doing. It seems that perhaps for the sake of readability and understanding the former is a better option (2-star pointer), but from a logical standpoint, what is the difference?