Pointers as function arguments in C
There are two main practical differences:
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 tostrtol()
, the contents of that pointer should point to the first character in the string that was not parsed to compute thelong
value. If you just passed the pointer tostrtol()
, 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.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 wheredouble
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 adouble **
, 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"?
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.
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.
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.