Call by Value and Call by Reference

In this tutorial, we will learn one of the most important concepts of C programming in detail known as call by value and call by reference. Let’s get started 💪💯


Introduction

There are two ways to call a function in C: 

  1. Call by value
  2. Call by reference

To understand the working and how they are different from each other, we need to first get ourselves familiar with the basics of Pointers. Pointers play a very important role in call by reference. We will demystify how in a moment.

Note:  pointers are one of the most important topics in C programming, and therefore it will be covered in depth later. Right now, our focus is on the basics to help us better understand the concept of call by reference.


Basics of Pointers

Let’s understand what pointers are anyway. 

Definition:  pointers are variables that can store the addresses of memory locations. 

Pointers are no different from variables. Instead of storing values, they store addresses. 

Consider the following definition:

int var = 10;

The C compiler will do the following for the above definition:

  • It will allocate 2 bytes of memory to var (assuming the computer takes 2 bytes to store an integer). 
  • Name “var” is associated with the newly assigned memory. 
  • Value 10 is stored in the allocated memory. 

The following figure shows that some memory block is assigned to variable var by the compiler with value 10 stored in it. Notice that the memory location has some address to.

Variable with memory address

We already know how to display the value of a variable. But, what if we want to display the address for some reason? We can use & (ampersand) which is called the “address of” operator as follows:

int var = 10;
printf(“Address of var: %llu”, &var);

Output:

32768

We can display an address as an unsigned integer by using %llu (long long unsigned) if it’s a 64 bit address. On a 32 bit machine, we can use %lu to display a 32 bit address. Some compilers may give warning that we are trying to display the memory address as an integer. To avoid this, we can use %p as the format specifier to display the address in a hexadecimal format. This format specifier is specially meant to print an actual memory address.

Try it yourself

Write a program to display the address in hexadecimal format of the following integer variable:

int a = 5;

Answer:

#include <stdio.h>

int main() {
    int a = 5;
    printf("%p", &a);
    return 0;
}

Output:

0x7fd6b44fb8

Explanation:

When I executed the program, I got 0x7fd6b44fb8 as the hexadecimal address of the variable a. You may get a different address, so need not worry if my output is not matching with yours.

X-X-X

Now, what if I create a pointer and store the address of the variable var in it? Then, instead of directly using the variable var, we can refer to it through a pointer.

You might be thinking at this moment, what’s the point of doing all this? Please bear with me. I promise I will not leave you in the middle of the road. The importance of doing all this hard work will be completely clear by the end of this article and believe me it’s worth it 💯

Let’s now create the pointer to the integer variable var as follows:

int *p;
p = &var; 

In line number 1, the pointer p is declared and in line number 2, the address of the variable var is assigned to it. Notice that pointer p is just like a normal variable, it has the data type, the name of the variable, but to differentiate it with a normal variable, an asterisk is added before the name. But why an asterisk? This symbol has a special meaning. We know, if we use asterisk with two operands, it will act as the multiplication operator just like the following:

int a, b=10, c=20;
c = a * b;

But if we use an asterisk with a single operand, it acts differently. It no longer acts as the multiplication operator, it becomes what is called the value at address operator. So, the declaration int *p; means “the value at address stored in p is an integer.” This means pointer p is holding the address of some integer value. 

Note:  when we declare a pointer, the data type specified is not the data type of the pointer itself, it represents the type of data whose address is stored in it (the major difference between a normal variable and a pointer worth noting). In our example, p is the pointer to integer value stored in the variable var as p is holding the address of var. 

Apart from declaring a pointer, an asterisk is used to access the value stored in a specific memory location. That’s why it is called the “value at address” operator. The following code snippet demonstrates the use of asterisk (*):

int a = 5;
printf(“%d”, *(&a));

Output:

5

First, by using the ampersand (address of) operator, we can get the address of the variable a. Then, by using the asterisk (value at address) operator we can get the value stored at the address of a which is 5 according to the example. Therefore, 5 is displayed on the screen. 

Note that there is no need to do this much work to access the value stored in variable a. Instead, we can directly use the name of the variable as follows:

int a = 5;
printf(“%d”, a);

Output:

5

At this point, you know two different ways to access the value of a variable. Great 😃

Congratulations 👏 we have come to the end of this section. Don’t worry if some concepts are unclear. As we progress, we will revisit the basics many times throughout the course. For now, just keep the following points in mind:

  • Pointers are similar to variables, but instead of storing data values, they store memory addresses. 
  • The ampersand (&) is called the “address of” operator and it is used to access the memory address.
  • The asterisk (*) is called the “value at address” operator and it is used to access the value stored at a specific memory location. 
  • Apart from accessing the value at address, asterisk (*) is also used to declare pointers like int *p;

Try it yourself

Consider the address of a is 0061FF1C and the address of p is 0061FF18. Determine the output of the following program:

#include <stdio.h>

int main()
{
    int a = 10;
    int *p = &a;

    printf("%p\n", &a);
    printf("%d\n", *(&a));
    printf("%p\n", p);
    printf("%d\n", *p);
    printf("%p\n", &(*p));
    printf("%p\n", &p);
    printf("%d\n", *(*(&p)));
    return 0;
}

Please try solving the above problem on your own first, and then look at the solution for comparison purposes.

Solution:

The output is as follows:

0061FF1C
10
0061FF1C
10
0061FF1C
0061FF18
10

The explanation is as follows:

#include <stdio.h>

int main()
{
    int a = 10;  // variable a of type int holding value 10.
    int *p = &a;   //p is the pointer to the integer variable a means it is storing the address of a.

    printf("%p\n", &a);
    printf("%d\n", *(&a));
    printf("%p\n", p);
    printf("%d\n", *p);
    printf("%p\n", &(*p));
    printf("%p\n", &p);
    printf("%d\n", *(*(&p)));
    return 0;
}
  • &a -> address of a.
  • *(&a) -> value at address of a.
  • p -> value of p (which is the address of a)
  • *p -> value at address stored in p.
  • &(*p) -> address of the value at address stored in p (which is same as the address of a.
  • &p -> address of p.
  •  *(*(&p)) -> &p means address of p. *(&p) means get the value stored at the address of p which is the same as the content of p. That is, we can replace *(&p) by p. So, *(*(&p)) can be written as *p which is the same as the value at address stored in p. As p is storing the address of a, value 10 will be displayed.

Formal Parameters vs. Actual Arguments

Before moving forward, let’s discuss the two main terms related to function calls:

  1. Actual arguments
  2. Formal parameters

Actual arguments:  these are the values passed to a function by its caller. 

Formal parameters:  these are the variables at the receiving end. They receive values passed by the caller in the function definition. 

The following example shows the formal and actual parameters in action:

#include <stdio.h>

int add(int a, int b) // formal parameters (variables) where a = 10 and b = 20.
{
    return a + b;
}

int main() {
    int result = add(10, 20); // actual parameters (values 10 and 20) passed by the caller.
    printf("%d", result);
    return 0;
}

Output:

30

Points to remember

  • Formal parameters are sometimes referred to as parameters and actual arguments as arguments. You may find (online and in books) that parameters and arguments are used interchangeably and it might be confusing. As long as the concept is clear, it doesn’t matter what names we are using to refer to them.
  • Formal parameters can only be variables (or pointers) as they are meant to receive values. They can never be simple values. On the other hand, actual parameters can be simple values, variables, or even expressions that boil down to simple values. 
  • Formal parameters are local to the function in which they are defined. This means they are not accessible outside the function. 
  • Any number of actual and formal parameters can be passed to a function. There is no upper limit. 
  • The formal and the actual parameters can be of any type. 

Check your understanding

Is the following program valid?

#include <stdio.h>

int fun(int a, int b, char c)
{
    return a + b + c;
}

int main() {
    int x = 3 + 5 - 10 * 10 / 2;
    printf("%d", fun(x, 67 / 3, 'a'));
    return 0;
}

Answer: 

Yes. The above program is absolutely valid. Formal and actual arguments can be of any type. 

The result is evaluated as follows:

  • x = -42 (after evaluating the expression) and therefore, variable a (the formal parameter) is -42.
  • 67/3 = 22 which is passed to the formal parameter b. 
  • character ‘a’ is passed to the formal parameter c. 
  • The result of a + b + c needs to be an integer because the return type of the function is integer. Therefore, the ASCII value of character ‘a’ is added which is 97. So, a + b + c = -42 + 22 + 97 = 77. 
  • So, 77 is the final result.

Call By Value

We have already learned how to call a function in the introduction to functions tutorial. There we took an example of adding two numbers. Let’s use the same example to understand the concept of call by value:

#include <stdio.h>

int add(int a, int b) {
    int sum;
    sum = a + b;
    return sum;
}

int main() {
    int result;
    result = add(10, 20);
    printf("Result: %d", result);
    return 0;
}

In the above example, we are calling the add() function within main() and we are passing values 10 and 20 to the variables a and b respectively. This is called call by value. As the name suggests, call by value means calling a function and passing values to it as actual arguments. 

This one is easy to understand, but what if we pass variables as actual arguments? Passing variables as arguments does not make any difference. See the modified version of the previous example:

#include <stdio.h>

int add(int a, int b) {
    int sum;
    sum = a + b;
    return sum;
}

int main() {
    int result, x = 10, y = 20;
    result = add(x, y);
    printf("Result: %d", result);
    return 0;
}

This time the actual arguments are variables, and passing variables as arguments is the same as passing their values. So, this is also called by value.

Now, as we have understood call by value, we are fully ready to understand the concept of call by reference.


Call by Reference

Calling a function by reference means passing addresses (in place of values) of variables as actual arguments. By using & (address of) operator, we can access the address of a variable. Consider the following example for better understanding:

#include <stdio.h>

int add(int *a, int *b) {
    int sum;
    sum = *a + *b;
    return sum;
}

int main() {
    int result, x = 10, y = 20;
    result = add(&x, &y);
    printf("Result: %d", result);
    return 0;
}

Output:

Result: 30

Explanation:

It can be observed that within the main() function, we are calling the add() function, but this time we are passing the addresses of x and y, and not values, as arguments. But in the receiving end, we cannot have simple variables to store addresses, we need pointers. In the basics of pointers section, we learned how pointers are used to hold addresses. So, that’s why in the receiving end, pointers a and b are declared and they will receive addresses from the caller in the same order in which they are passed as arguments.

Apart from this, within the add() function you can observe asterisk (*) is placed in front of a and b in the expression sum = *a + *b. It is important to do this because a and b are not normal variables, they are pointers holding addresses of variables x and y. And as we learned in the basics of pointers section, asterisk (*) is called the “value at address” operator. As a is the pointer to variable x in the main() function, using an asterisk in front of it will give the value stored in variable x. Similarly, b is the pointer to variable y, and hence using an asterisk in front of it will give the value stored in variable y. So, sum = *a + *b is the same as sum = 10 + 20 = 30. This value will be returned to the caller and that’s why we got 30 as the output.

But why are we doing all this hard work? Passing addresses and then accessing the values stored in those addresses does not make any sense. Aren’t we happy with call by value? After all, call by value is pretty straightforward. 

We definitely have done a lot of work in the last example to access values of variables x and y. Passing by value makes more sense. But believe me, all the effort made so far is not wasted. The importance of call by reference will be evident through a popular use case in programming – swapping two numbers. So, let’s dive in.


Use Case: Swapping Two Numbers

Let’s say we have two numbers 10 and 20 within variables x and y respectively inside the main() function. We are required to define a function which is capable of swapping values within variables x and y. This means after the function completes its execution, variable x will hold value 20 and y will hold value 10. How to achieve this?

To swap numbers, we can use a temporary variable as follows:

// Considering x and y are already defined.
int temp; 
temp = x;
x = y;
y = temp;

After executing temp = x, variable temp will hold value 10. Because of x = y, the value of y is assigned to x and this means the new value of x is 20. Now, we can assign value 10 to variable y using the statement y = temp. In this way, the two numbers are swapped. 

Before execution of the code snippet:

x = 10, y = 20

After execution of the code snippet:

x = 20, y = 10

To verify, run the following program on your computer:

#include <stdio.h>

int main() {
    int x = 10, y = 20, temp;
    
    temp = x;
    x = y;
    y = temp;
    
    printf("x = %d, y = %d", x, y);
    return 0;
}

Output:

x = 20, y = 10

See! It works 😌

But, we have been asked to define a function to handle swapping. So, let’s create the function swap() and include the code snippet we just saw to swap two numbers within the swap() function as follows:

void swap(int x, int y) { 
    int temp; 
    temp = x;
    x = y;
    y = temp;
}

Notice that the return type of the swap() function is void because this time we are not returning the result. We are just swapping numbers.

Now, within the main() function we need to call the above function and pass appropriate values to the parameters. The complete program is as follows:

#include <stdio.h>

void swap(int x, int y) { 
    int temp; 
    temp = x;
    x = y;
    y = temp;
}

int main() {
    int x = 10, y = 20;
    swap(x, y);
    printf("x = %d, y = %d", x, y);
    return 0;
}

Output:

x = 10, y = 20

Explanation:

It seems like the numbers are not swapped. Variables x and y are still holding their initial values. What’s going on here? We know the logic we have written to swap values is correct (as we verified this already). It works within the main() function, but not within the swap() function. Why? The answer lies within the fact that we are calling the swap() function by value. This is the root cause of the problem. Let’s understand properly why call by value is the problem. 

Call by value means calling a function and passing values of the variables as actual arguments. While calling the swap() function, we are passing values of x and y which will be received by the parameters x and y of the swap function. These parameters are defined within the function, they are not the same as the variables defined within the main() function. So, in the swap() function, x and y are newly defined variables which have received values 10 and 20 respectively. Now, within the swap() function, due to the swap code, the numbers 10 and 20 are swapped within these variables only. Variables x and y defined in the main() function are unaffected by this change. The following illustration demonstrates the same:

Call By Value

As soon as the swap() function finishes execution, variables x and y defined as parameters will be destroyed, and variables defined in the main() remain unaffected by the work done in the swap() function. Conclusively we can say the swap() function we have defined is useless 😔

You might be thinking why are we not returning x and y values from the function? This is because a function can return only one value. Here, the requirement is to return values stored in x and y both. So, we are not left with this option. 

Then, what’s the way out? A simple way out is calling a function by reference 🥳. Consider the following program:

#include <stdio.h>

void swap(int *p, int *q) {
    int temp;
    temp = *p;
    *p = *q;
    *q = temp;
}

int main() {
    int x = 10, y = 20;
    swap(&x, &y);
    printf("x = %d, y = %d", x, y);
    return 0;
}

Output:

x = 20, y = 10

Explanation:
This time we got our desired output, but how? Understanding this concept is not difficult if you focus for a moment. Within the main() function, the swap() function is called, but this time we have used the address of (&) operator in front of variables x and y to pass the addresses of x and y. This is call by reference. We are not passing values this time, we are passing addresses (in other words, references). In the receiving end (i.e. in the swap() function), parameters are not variables, they are pointers. As mentioned earlier, to receive addresses, we need pointers. Simple! So, pointers p will hold the address of x and pointer q will hold the address of y. 

Now comes the most interesting part. This is where all the magic has happened. Let’s understand each line properly.

  • int temp
    • Variable temp is declared as an integer normal variable to temporarily hold a value for swapping. 
  • temp = *p
    • *p means “get the value at the address stored in p”. 
    • As p is holding the address of x, we are accessing the value of x here.
    • This value is now provided to the temp variable using the assignment operator (=). 
    • The value of temp is 10. 
  • *p = *q
    • *q means “get the value at the address stored in q” which is 20. This means we are getting the value of the variable y defined in the main() function. 
    • *p in the left hand side of the assignment operator means “go to the address stored in p; don’t get the value”. This is because the LHS of the assignment operator cannot be a simple value. So, *p means “just go inside variable x” as p is holding the address of x.
    • So, the entire statement means “go inside the variable x in the main() function and store the value of y in it.” 
    • In this way, we are directly dealing with the variables x and y defined in the main() function. 
    • Now, variable x of the main() function is holding value 20.
  • *q = temp
    • As *q is in the LHS of the assignment operator, it represents variable y in the main() function, but it will not be replaced by its value. 
    • The entire statement “get the value of temp (which is 10) and store it in the variable y”.
    • So, the final value of y is 10.

 The entire process is summarized in the following illustration:

Call By Reference

So, in this way, we are making changes to the original variables by using call by reference. From all this discussion we can understand that call by reference is useful in situations when multiple variables of some function need to be affected, as the result of some operations performed on them, in some other function. This is the use case of call by reference 🙂


When to use what?

Use call by value when you don’t want the original variables to change. It is useful when you just want the copy of the variables. 

Use call be reference when you want the changes to reflect in the original variables, just like in case of swapping two numbers.


Check Your Understanding

Question 1

How to get the address of the following variable?

int var = 10
By using & after the variable name.
By using * after the variable name.
By using & in front of the variable name.
By using * in front of the variable name.
explanation

ampersand (&) must be used before the variable name to get its address.

Question 2

Which of the following is the correct way to declare a pointer in C?

int *p;
int p*;
int p;
* int p;
explanation

int *p; is the only valid way out of the ways given in the options. Although, we are allowed to add spaces in between the way we want. For example, int *p, int* p; int * p; are all valid ways to declare the pointer p.

Question 3

What is the main disadvantage of call by value which is also the advantage of call by reference?

call by value is complex to implement whereas call by reference is not.
call by value uses more memory compared to call by reference.
call by value does allow modification of original variables but call by reference does not.
Call by value is slower than call by reference.
explanation

call by value uses more memory because every time when a function is called by value, the copies of the actual values are passed as arguments and at the receiving end, new variables (parameters) should be defined to receive these values. On the other hand, in call by reference, the original variables are referred to in the called function in place of creating new variables.

Your score is out of


Conclusion

In C programming, actual arguments can be passed to a function in two ways: by value or by reference. By default, functions are called by value. If we want to call a function by reference, then we need to pass addresses as actual arguments. In this tutorial, we learned the significance of the address-of (&) operator, the differences between call by value and call by reference, and in which situations call by value and the situations where each method is useful. I hope I made the concepts clear. If you have any doubts, then post your doubts in the comment section of this tutorial.



Leave a comment

Leave a comment

Your email address will not be published. Required fields are marked *

Thank you for choosing to leave a comment. Please be aware that all comments are moderated in accordance with our policy. For more information, please review our comments policy. Rest assured that your email address will not be shared with anyone. Kindly refrain from using keywords in your comment. Let’s maintain a respectful atmosphere and engage in meaningful conversations.