Skip to main content

Welcome to a Grasp lesson

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

Lesson 4

Dereferencing Pointers

Recommended resources

Hello! Welcome to your fourth lesson on C pointers.

In the last lesson, we focused on the address-of operator (&), which allows us to obtain the memory address of a variable and store it in a pointer. You now know how to make a pointer "point to" a specific piece of data.

Today, we'll answer the complementary question: once a pointer holds an address, how do we use it to access and manipulate the data at that address? This lesson introduces the dereference operator (*), the tool that lets you "follow" a pointer to its target. Our learning outcome is to use the dereference operator (*) to access and modify values through pointers. This is the key to unlocking the power of indirection in C.

The Dereference Operator: *

The dereference operator, also denoted by an asterisk (*), is the inverse of the address-of operator (&). If you have a pointer that holds an address, applying the * operator to the pointer gives you access to the value stored at that address.

Think of it this way:

  • &variable gives you the address of variable.
  • If pointer = &variable, then *pointer gives you the value of variable.

This operator can be used in two primary ways: to read a value and to write a value.

1. Reading a Value (Right-Hand Side)

When you use the dereference operator in an expression or on the right-hand side of an assignment, you are reading the value from the memory location the pointer points to.

int sensor_reading = 980;       // Original data, e.g., a pressure reading in pascals
int* reading_ptr = &sensor_reading; // Pointer holds the address of sensor_reading
int another_variable;

another_variable = *reading_ptr; // Read the value at the address held by reading_ptr

// Now, another_variable contains the value 980.

2. Modifying a Value (Left-Hand Side)

This is where pointers become truly powerful. When you use the dereference operator on the left-hand side of an assignment, you are writing a new value to the memory location the pointer points to. You are modifying the original variable's data indirectly.

int sensor_reading = 980;
int* reading_ptr = &sensor_reading;

*reading_ptr = 1013; // Write the value 1013 to the address held by reading_ptr

// The original variable, sensor_reading, now contains the value 1013.

This "remote control" modification is fundamental in systems programming, especially in contexts you're familiar with, like having a function in a communication stack fill a buffer provided by a calling application.

This image provides a great visual of this process:

How Pointers Work in C: Dereferencing and Pointers to Pointers
This diagram illustrates the effect of dereferencing. Initially, `var` holds 10. A pointer `ptr` stores the address of `var`. The statement `*ptr = 20;` follows the pointer and overwrites the value at that address, changing `var` to 20. The subsequent double-pointer operation does the same, changing the value to 30.

Distinguishing the Pointer from its Target

A critical concept to master is the difference between changing the pointer itself and changing the value it points to.

  • ptr = &new_variable; changes where the pointer points.
  • *ptr = new_value; changes the value at the location the pointer currently points to.

The following reading explains this distinction with an excellent code example.

Arrays & Pointers

The article 'Arrays & Pointers' from Cornell University provides a clear explanation of dereferencing for both reading and writing, and highlights the crucial difference between assigning to a pointer versus assigning to its dereferenced value.

Please read the section titled 'Pointers as References, and Dereferencing'. Focus on the two code examples. The first shows how *ptr = 41; modifies x. The second, more detailed example, demonstrates how ptr = &y; changes what the pointer points to, while *ptr = 20; then modifies the new target, y.

Test your understanding!

Analyze the following code snippet and predict the final values of can_id and priority after the program runs.

#include <stdio.h>

int main() {
    int can_id = 0x18FF;
    int priority = 3;
    
    int* data_ptr = &can_id;
    
    *data_ptr = 0x7E8; // Step 1
    
    data_ptr = &priority; // Step 2
    
    *data_ptr = 1; // Step 3

    printf("CAN ID: 0x%X\n", can_id);
    printf("Priority: %d\n", priority);
    
    return 0;
}
Show answer
  • Step 1: *data_ptr = 0x7E8;
    data_ptr points to can_id. Dereferencing it modifies can_id.
    can_id becomes 0x7E8. priority is still 3.

  • Step 2: data_ptr = &priority;
    This changes the pointer itself. data_ptr now holds the address of priority. It no longer points to can_id.

  • Step 3: *data_ptr = 1;
    data_ptr points to priority. Dereferencing it modifies priority.
    priority becomes 1. can_id is unaffected and remains 0x7E8.

Final values:

  • CAN ID: 0x7E8
  • Priority: 1

Practical Application: Simulating Pass-by-Reference

As we touched on previously, C passes all function arguments by value. This means a function receives a copy of the data. If the function modifies its copy, the caller's original variable is unchanged.

Your work in automotive systems often requires a function to return status or data to the caller, not just through its return value. For instance, an AUTOSAR COM function might need to update a signal value in a PDU (Protocol Data Unit) buffer that the application provides.

By passing a pointer, you are still passing a value—the value of the pointer is the memory address. But by dereferencing that pointer inside the function, you gain the ability to modify the original data in the caller's scope. This is how C simulates pass-by-reference.

The classic swap function is the canonical example of this pattern.

Arrays & Pointers

The 'Arrays & Pointers' article also demonstrates this concept perfectly with two versions of a swap function.

Read the section 'Pass by Reference' under 'Fun Pointer Tricks'. Compare the non-working version that passes ints by value with the working version that passes int* pointers and uses dereferencing to swap the original values.

This pattern extends to any data type, including complex structs. When dealing with pointers to structures, you must dereference the pointer before accessing a member field.

typedef struct {
    uint32_t id;
    uint8_t dlc; // Data Length Code
} CAN_Frame;

CAN_Frame frame;
CAN_Frame* frame_ptr = &frame;

// To access the 'id' member through the pointer:
(*frame_ptr).id = 0x123; 

The parentheses are necessary because the . operator has higher precedence than the * operator. Because this syntax is clumsy, C provides a convenient shortcut: the arrow operator ->.

frame_ptr->id = 0x123; is exactly equivalent to (*frame_ptr).id = 0x123;.

Given your work with complex data structures in AUTOSAR, you'll find the -> operator is ubiquitous in C code that handles anything more complex than primitive types. The document "C Pointers" from Cornell's ECE department shows this syntax clearly in its section on pointers to structs.


Conclusion

Today you've learned the second fundamental pointer operation: dereferencing. While & lets you get an address, * lets you use that address to interact with the data stored there.

Key Takeaways:

  • The dereference operator (*) follows a pointer to its memory address, allowing you to access the value stored there.
  • Using *pointer on the right side of an assignment reads the value.
  • Using *pointer on the left side of an assignment writes to the value, modifying the original data.
  • This mechanism is the foundation for simulating pass-by-reference in C, a critical pattern for writing functions that need to modify their inputs.
  • It's crucial to distinguish between operations on the pointer itself (changing where it points) and operations on the dereferenced pointer (changing the data it points to).

In our next lesson, we will formalize the concepts of pointer declaration and initialization for different data types. We'll also cover a topic that is highly relevant to your work with different microcontroller architectures: pointer size and how it relates to the system's architecture (32-bit vs. 64-bit). This will lay the groundwork for understanding pointer arithmetic.