Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 14

Modifying Caller Variables with Pointers

Recommended resources

Hello! Welcome to the first lesson in our new module, "Pointers and Functions."

In our last session, we saw how to implement our own versions of standard string functions like strcpy. When we wrote void my_strcpy(char *dest, const char *src), we were already using today's core concept: we passed a pointer (dest) so that the my_strcpy function could write data into a buffer that was created in the caller's scope.

Today, we will formalize this powerful pattern. The goal of this lesson is to master how to pass pointers to functions to enable the modification of a caller's variables. We'll explore why this is necessary by examining C's default "pass-by-value" behavior and then implement the "pass-by-pointer" solution. This is a foundational technique in C, essential for writing efficient and functional code, especially in contexts like yours where functions must operate on shared data structures, like a communication buffer or a shared state variable in an AUTOSAR component.

The Problem: Why Functions Can't Normally Modify Their Arguments

By default, when you pass a variable to a function in C, you are passing a copy of its value. The function works on this local copy, and any changes it makes are discarded when the function returns. The original variable in the calling function remains completely untouched. This mechanism is known as pass-by-value.

Let's see a classic example of this in action. The following video provides an excellent illustration of an increment function that doesn't work as expected due to pass-by-value semantics. It then visualizes the underlying memory behavior using stack frames, which will build on your existing knowledge of C's memory model.

Pointers as function arguments - call by reference

This video from mycodeschool, 'Pointers as function arguments - call by reference', first demonstrates the problem with pass-by-value and then explains the memory layout with stack frames.

Please watch from 00:41 to 02:20 to see the initial problem. Then, continue from 04:21 to 11:02 for a detailed explanation of function call stacks and why the local copy is separate from the original variable.

As the video explains, each function call gets its own "stack frame"—a private area of memory for its local variables and parameters. The increment function was only modifying the value in its own stack frame, which is destroyed upon function exit.


The Solution: Passing Pointers

To allow a function to modify a variable in its caller's scope, we don't pass the variable's value; we pass its address. The function then receives a pointer to the original variable. By dereferencing this pointer, the function can reach back into the caller's stack frame and modify the original data. This is often called "pass-by-reference" or, more accurately in C, pass-by-pointer.

Here are the three essential steps:

  1. Define the function parameter as a pointer: The function signature must declare the parameter as a pointer to the correct data type (e.g., void func(int *p)).
  2. Pass the address as the argument: When calling the function, use the address-of operator (&) to pass the memory address of the variable (e.g., func(&my_var)).
  3. Dereference the pointer in the function: Inside the function, use the dereference operator (*) to access and modify the value at the stored address (e.g., *p = 100;).

Now, watch the final part of the same video to see this solution implemented.

Pointers as function arguments - call by reference

This segment demonstrates how to fix the increment function by passing the address of the variable and using a pointer parameter.

Watch from 11:02 to 13:56 to see how passing a pointer solves the problem. Pay close attention to how the address is passed and how the pointer is dereferenced to modify the original value.

The image below provides a complete, static view of this entire process, showing the C code, the corresponding memory layout, and the final output.

Passing Pointers to Function: Passing by Address Example
This diagram illustrates the "pass-by-pointer" mechanism. The `main` function passes the address of `num1` to `fun1`. The pointer parameter `p` inside `fun1` now holds the address of `num1`. By dereferencing `p` (i.e., `*p = 20`), the function modifies the original `num1` variable in `main`'s scope.

A Crucial Clarification: C is Always Pass-by-Value

It's important to be precise: C does not have true pass-by-reference like C++. C is always a pass-by-value language.

When we pass a pointer to a function, we are passing the value of the pointer (the memory address) by value. This means the function receives a copy of the memory address. While changing the pointer parameter itself inside the function (e.g., making it point to a different address) won't affect the original pointer in the caller, using the copied address to dereference allows modification of the data at that address.

The following resource explains this subtle but important distinction and provides a clean, step-by-step guide to implementing the pass-by-pointer pattern.

Pointers and Functions

The article 'Pointers and Functions' from Dive into Systems offers a rigorous explanation of the pass-by-pointer pattern and a complete code example.

Read the sections 'Pointers and Functions' and the boxed text 'Pass by Value'. Then, carefully review the 3-step implementation guide and the full code example that follows. This will solidify your understanding of the mechanics.

Test your understanding!

The following swap function is intended to swap the values of two integer variables, but it doesn't work.

#include <stdio.h>

void swap(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 10;
    int y = 20;
    printf("Before swap: x = %d, y = %d\n", x, y);
    swap(x, y);
    printf("After swap: x = %d, y = %d\n", x, y); // This will show x and y are unchanged
    return 0;
}

Based on what you've just learned, modify the swap function and the call to it in main so that it correctly swaps the values of x and y.

Show answer

Here is the corrected code. The swap function's parameters are changed to pointers, we dereference them to manipulate the values, and we pass the addresses of x and y from main.

#include <stdio.h>

// 1. Change parameters to pointers
void swap(int *a, int *b) {
    // 3. Dereference pointers to access the original values
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 10;
    int y = 20;
    printf("Before swap: x = %d, y = %d\n", x, y);
    
    // 2. Pass the addresses of the variables
    swap(&x, &y); 
    
    printf("After swap: x = %d, y = %d\n", x, y); // Correctly prints x = 20, y = 10
    return 0;
}

Application: Modifying Complex Data in Place

This technique is not just for simple types like int. In your work with AUTOSAR, you frequently deal with complex struct types for message payloads or component configurations. Passing a pointer to a large struct is far more efficient than passing it by value, which would require copying the entire structure onto the stack.

When you have a pointer to a struct, you can access its members using the arrow operator (->). This operator is simply syntactic sugar for dereferencing the pointer and then accessing the member.
st_ptr->age is equivalent to (*st_ptr).age.

The following resource demonstrates passing a pointer to a struct to a function.

A TUTORIAL ON POINTERS AND ARRAYS IN C by Ted ...

This excerpt from 'A Tutorial on Pointers and Arrays in C' shows how to define a function that accepts a pointer to a struct and how to access its members using the -> operator.

Read the section discussing passing pointers to structures, starting from the text 'If we have a large number of employees...' until the end of the program 5.2 code block. Notice how the show_name function takes struct tag *p as an argument and uses p->fname to access the member.

To make the modification aspect explicit, imagine the show_name function from the example also needed to update the employee's age:

void show_name_and_increment_age(struct tag *p) {
    printf("\n%s ", p->fname); 
    printf("%s, ", p->lname);
    printf("Age: %d\n", p->age);

    // Modify the original structure's data through the pointer
    p->age++; 
}

Calling show_name_and_increment_age(st_ptr) would now permanently increment the age in the my_struct variable located in the main function.


Conclusion

You have now mastered one of the most fundamental and powerful idioms in C programming. By understanding the default pass-by-value mechanism and how to overcome its limitations with pointers, you can write functions that efficiently modify data anywhere in your program.

Key Takeaways:

  • By default, C passes arguments by value, meaning functions operate on local copies and cannot alter the original variables.
  • To modify a caller's variable, you must pass a pointer to it (i.e., pass its address using the & operator).
  • The function must be defined to accept a pointer parameter (*).
  • Inside the function, you must dereference the pointer (* for basic types, -> for structs) to access and modify the data at the original address.
  • This "pass-by-pointer" technique is crucial for both functionality (modifying caller data) and performance (avoiding large data copies on the stack).

In our next lesson, we will continue exploring the relationship between pointers and functions. We'll delve into the implications of returning pointers from functions, a technique that is powerful but also fraught with potential dangers, such as returning pointers to local variables that no longer exist. This is a critical topic for writing robust and safe code, directly relevant to the functional safety standards you work with.