CodingBison

When we call a function in C, typically, we pass a set of parameters to that function. With respect to pointers, there are three ways in which a caller can can pass parameters to the functions: (1) by value, (2) by reference, and, (3) by reference of the reference. These methods serve different purposes and it is important to understand them in detail.

Passing by Value

When we pass a parameter by value, then we pass a copy of the variable to the called function.

Let us go through an example (provided below) to understand this better. Here, the main() function calls pass_variable_param() and instead of passing actual x, it passes a copy of x. In this example, var_temp is nothing but a copy of x.

 #include <stdio.h>

 void pass_variable_param (int var_temp) {
     printf("\t[%s] Passed value is %d (address: %p)\n", 
             __FUNCTION__, var_temp, &var_temp); 
     var_temp *= 10;
     printf("\t[%s] Updated value is %d (address: %p)\n", 
             __FUNCTION__, var_temp, &var_temp); 
 }

 int main () {
     int x = 100;

     printf("[%s] Before function call, value is %d (address: %p)\n", 
             __FUNCTION__, x, &x);
     pass_variable_param(x);
     printf("[%s] After function call, value is %d (address: %p)\n", 
             __FUNCTION__, x, &x);
 }

The output (provided below) shows that even though we change the value of var_temp in the function pass_variable_param(), the value of x in main() remains unchanged. This is the expected behavior, since after all, we are passing a copy of x and changes to a copy would not be reflected in variable x. Another proof of this behavior is that x and its copy (var_temp) have different addresses.

 [main] Before function call, value is 100 (address: 0xbfc9b4ec) 
 	[pass_variable_param] Passed value is 100 (address: 0xbfc9b4d0) 
 	[pass_variable_param] Updated value is 1000 (address: 0xbfc9b4d0) 
 [main] After function call, value is 100 (address: 0xbfc9b4ec) 

Passing by Reference

When we pass reference (aka address) of a variable instead of its value, then we are actually passing the address of the variable. Thus, the called function can manipulate the value stored at the address instead of manipulating a copy of the variable defined in the calling function. This approach allows the change made in the called function to become visible in the caller function as well.

Therefore, if we wish that the changes made in the called function become visible in the caller function, then we need to pass by reference instead of passing by value.

With that, let us rewrite the earlier example such that the main() function passes the variable by reference instead of by value. This allows the called function (pass_pointer_param()) to directly manipulate the value stored at the address of x, which means x itself.

 #include <stdio.h>

 void pass_pointer_param (int *ptr_temp) {
     printf("\t[%s] Passed value is %d (address: %p)\n", 
         __FUNCTION__, *ptr_temp, ptr_temp); 
     *ptr_temp *= 10;
     printf("\t[%s] Updated value is %d (address: %p)\n", 
         __FUNCTION__, *ptr_temp, ptr_temp); 
 }

 int main () {
     int ret_val, x = 100;

     printf("[%s] Before function call, value is %d (address: %p)\n", 
         __FUNCTION__, x, &x);
     pass_pointer_param(&x);
     printf("[%s] After function call, value is %d (address: %p)\n", 
         __FUNCTION__, x, &x);
     return 0;
 }

When we see the output, we find that the changed value gets reflected in the main() function as well. Also, both functions see the same address for the variable.

 [main] Before function call, value is 100 (address: 0xbfe585dc)
 	[pass_pointer_param] Passed value is 100 (address: 0xbfe585dc)
 	[pass_pointer_param] Updated value is 1000 (address: 0xbfe585dc)
 [main] After function call, value is 1000 (address: 0xbfe585dc)

Before we move on, we would like to reflect on a few miscellaneous but related behavior of using pointers as functions params.

First, this method is especially useful when we pass a variable that has a much bigger storage as compared to a simple integer in the above code; an example of such storage could be a data structure that has a large size. Passing a large variable by value can be inefficient since we need to make a copy of the variable before passing it.

Second, the same logic applies when the function returns a value. If we are dealing with a large data structure, then without passing by reference, the function would end up passing by value, which would mean making another copy.

Third, functions have an inherent limitation of being able to return a single value. Using references, we can pass multiple variables and the function can update values of these variables within the function. In this way, we can break the inherent limitation of functions returning a single value.

Lastly, it would be a good place to point out that when we pass an array to a function, then C passes them by reference instead of value. If you have ever wondered why sizeof() does not give the correct size of the array in a called function, then this is the reason! The called function only gets a pointer and so calling a sizeof() on the passed array would simply return the size of the pointer. Needless to say, passing by reference is an efficient design since a C array can contain a large number of elements and passing them by value would mean making a copy of all those values with each call.

Passing Reference of the Reference

This approach is useful when one has to manipulate the address of the passed pointer itself in the called function; in such a case, passing by reference would not work.

In case you are wondering, what can be a possible use-case of passing a reference of reference, then, here is one. Let us say, we have a requirement for manipulating a pointer in the called function -- there can be cases, when we do a malloc() inside the called function and not in the caller function. For example, we can pass a pointer and make it point to a data structure allocated in the called function. Once the execution of the called function is complete, then we would like to get the handle of the new pointer instead of the earlier passed pointer; the earlier pointer might as well be pointing to NULL!

Let us use our earlier example to demonstrate the need for passing a reference of a reference. We start by providing an example that incorrectly attempts to use the address of a malloced pointer from outside the called function.

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

 void pass_pointer_param_wrong (int *ptr_temp) {
     printf("\t[%s] Passed pointer address is %p\n", 
             __FUNCTION__, ptr_temp); 
     ptr_temp = (int *)malloc(sizeof(int)); 
     printf("\t[%s] Updated pointer address is %p\n", 
             __FUNCTION__, ptr_temp); 
 }

 int main () {
     int *ptr_int = NULL;

     printf("[%s] Before function call, pointer address is %p\n", 
         __FUNCTION__, ptr_int);
     pass_pointer_param_wrong(ptr_int);
     printf("[%s] After function call, pointer address is %p\n", 
         __FUNCTION__, ptr_int);
     return 0;
 }

When we do a malloc in pass_pointer_param_wrong(), ptr_temp points to a new address. However, since we are passing only the address of the pointer variable, a change in the address of the pointer in pass_pointer_param_wrong() does not get reflected in the calling function, main(). The output of this program demonstrates this failure:

 [main] Before function call, pointer address is (nil)
 	[pass_pointer_param_wrong] Passed pointer address is (nil)
 	[pass_pointer_param_wrong] Updated pointer address is 0x945e008
 [main] After function call, pointer address is (nil)

In the above example, the malloc() call changes the address pointed to by the pointer from (nil) to 0x9890008. However, when the call returns back to main(), the information of the new address is lost. This is not all! What is worse is that because the main() function does not have a pointer to the malloced data and so it cannot call free() -- there are very few programming mistakes that are as grave as not freeing an allocated memory!

To avoid this, we need to pass a double pointer as a parameter. We provide the modified code. Not only does this method correctly reflects the new address in the main() function, it also frees the allocated memory!

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

 void pass_pointer_param_correct (int **ptr_temp) {
     printf("\t[%s] Passed value of the double pointer is %p\n", 
         __FUNCTION__, *ptr_temp); 
     *ptr_temp = (int *) malloc(sizeof(int)); 
     printf("\t[%s] Updated value of the double pointer is %p\n", 
         __FUNCTION__, *ptr_temp); 
 }

 int main () {
     int *ptr_int = NULL;
     int **ptr_ptr_int = &ptr_int;

     ptr_ptr_int = &ptr_int;
     printf("[%s] Before function call, pointer address is %p\n",
         __FUNCTION__, ptr_int);
     printf("[%s] Before function call, address of double-pointer is %p\n",
         __FUNCTION__, ptr_ptr_int);

     pass_pointer_param_correct(ptr_ptr_int);
     ptr_int = *ptr_ptr_int;

     printf("[%s] After function call, pointer address is %p\n",
         __FUNCTION__, ptr_int);
     printf("[%s] After function call, address of double-pointer is %p\n",
         __FUNCTION__, ptr_ptr_int);
     free(ptr_int); 
     return 0;
 }

Here is the program output:

 [main] Before function call, pointer address is (nil)
 [main] Before function call, address of double-pointer is 0xbf95e3e8
 	[pass_pointer_param_correct] Passed value of the double pointer is (nil)
 	[pass_pointer_param_correct] Updated value of the double pointer is 0x8b9d008
 [main] After function call, pointer address is 0x8b9d008
 [main] After function call, address of double-pointer is 0xbf95e3e8




comments powered by Disqus