Skip to main content

Welcome to a Grasp lesson

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

Lesson 45

Pointers to Const vs. Const Pointers

Recommended resources

Hello! Welcome to the first lesson in our new module, "Type Qualifiers and Const Correctness."

In our last lesson, we designed a plugin architecture, defining a clear API contract between a host application and its plugins using a struct of function pointers. That contract is a form of communication, but C doesn't, by default, prevent the plugin from accidentally modifying data it was only supposed to read. This is where type qualifiers, specifically const, become essential.

Today's learning outcome is to distinguish between a 'pointer to const data' and a 'const pointer'. Mastering this distinction is fundamental to writing safer, more readable, and self-documenting code—a practice that is especially critical in the high-integrity environments you work in, such as automotive systems governed by standards like ISO 26262 and MISRA-C.

We will explore how a single keyword, const, completely changes the behavior and guarantees of a pointer based on its placement in a declaration.

The Two Flavors of const with Pointers

At its heart, a pointer involves two distinct entities:

  1. The pointer variable itself, which stores a memory address.
  2. The data at that memory address, which the pointer "points to".

The const qualifier can be applied to either of these, leading to two different meanings with very similar-looking syntax. To get a clear overview of these concepts, let's start with a short video that explains and demonstrates them with simple examples.

Constant Pointer VS. Pointer To A Constant | C Programming Tutorial

The video 'Constant Pointer VS. Pointer To A Constant' from Portfolio Courses provides a concise and clear explanation of the core concepts we're covering today.

Please watch the video from the beginning to 04:58, and then from 05:40 to the end. The video will walk you through: What a constant pointer is. What a pointer to a constant is. How to combine them into a constant pointer to a constant. A final summary of all possibilities.

Now, let's break down these concepts in detail, formalize the rules, and connect them to practical use cases.


1. Pointer to const Data

This is a non-constant pointer that points to constant data.

  • What it means: You cannot change the value of the data being pointed to through this pointer. However, you can change the pointer itself to point to a different memory address.

  • Syntax: The const keyword comes before the data type or between the data type and the asterisk (*). Both forms are identical to the compiler.

    const int *p;
    // is the same as
    int const *p; 
    

Let's see it in action:

int main() {
    int val1 = 10;
    int val2 = 20;

    const int *p_to_const = &val1; // p_to_const points to val1

    // ALLOWED: We can change where the pointer points.
    p_to_const = &val2; 
    printf("Value via pointer: %d\n", *p_to_const); // Prints 20

    // NOT ALLOWED: We cannot modify the data through the pointer.
    // *p_to_const = 30; // This line would cause a compile-time error.

    // However, the original data is not constant, so it can be changed directly.
    val2 = 30;
    printf("Value via pointer after direct change: %d\n", *p_to_const); // Prints 30

    return 0;
}

Key Use Case: This is extremely common for function parameters. When you pass a pointer to a function and you want to guarantee that the function will not modify your data, you declare the parameter as a pointer to const data. It’s a contract that enforces read-only access.

// This function promises not to change the content of 'buffer'.
void print_data(const char* buffer) {
    // ... logic to print buffer ...
}

2. const Pointer

This is a constant pointer that points to non-constant data.

  • What it means: The pointer itself is locked to a single memory address for its entire lifetime. It cannot be made to point somewhere else. However, you are free to change the value of the data it points to.

  • Syntax: The const keyword comes after the asterisk (*).

    int * const const_p;
    
  • Rule: A const pointer must be initialized when it is declared, because you can't assign it a value later.

Let's see the code:

int main() {
    int val1 = 10;
    int val2 = 20;

    // Must be initialized at declaration
    int * const const_p = &val1; 

    // ALLOWED: We can modify the data the pointer points to.
    *const_p = 50; 
    printf("val1 is now: %d\n", val1); // Prints 50

    // NOT ALLOWED: We cannot change where the pointer points.
    // const_p = &val2; // This line would cause a compile-time error.

    return 0;
}

Key Use Case: This is useful when you have a pointer that must always refer to the same object or hardware resource. For instance, in embedded systems, a pointer to a specific memory-mapped hardware register (like a UART data register) could be declared as volatile uint8_t * const UART_DATA_REG. The address of the register never changes, but its contents do.


A Simple Rule for Reading const Declarations

The placement of const can be confusing. Here's a simple and effective rule: read the declaration from right to left. Another way to think about it is that const applies to the item immediately to its left. If there's nothing to its left, it applies to the item on its right.

Let's apply this to our examples.

  1. int const * p;

    • Start with p: p is...
    • Look left, see *: p is a pointer to...
    • Look left, see const: p is a pointer to a const...
    • Look left, see int: p is a pointer to a const int.
    • Meaning: The int data is const.
  2. int * const p;

    • Start with p: p is...
    • Look left, see const: p is a const...
    • Look left, see *: p is a const pointer to...
    • Look left, see int: p is a const pointer to an int.
    • Meaning: The pointer * is const.

This "right-to-left" reading method is very powerful for parsing more complex C declarations, which we'll encounter later.

How to read C declarations

The article 'How to read C declarations' from educative.io provides an excellent summary and a handy 'Const-correctness quick reference' that reinforces this rule.

Please read the sections 'Explanation of Type Qualifier Rule' and 'Const-correctness quick reference'. This text formalizes the rule we just discussed: the qualifier binds to what it's next to. This is a great way to solidify your understanding.


3. Combining Them: const Pointer to const Data

You can also combine both qualifiers to create a pointer that is itself constant and points to data that is also constant.

  • Syntax: const int * const p;
  • Meaning: You can neither change where the pointer points, nor can you change the data it points to through the pointer.
Understanding `const` in C++ Pointer Declarations
This image breaks down the declaration `const double *const pip = π`. Reading right-to-left: `pip` is a `const` pointer to a `const double`. This means both the pointer `pip` and the `double` value it points to are immutable through this declaration.

Here is a code example:

int main() {
    int val = 10;

    const int * const p = &val;

    // NOT ALLOWED: Cannot modify the data.
    // *p = 20; // Compile-time error.

    // NOT ALLOWED: Cannot change the pointer.
    // int val2 = 30;
    // p = &val2; // Compile-time error.

    // The only thing you can do is read the value.
    printf("Value: %d\n", *p);

    return 0;
}
Test your understanding!

For each of the following declarations, state whether p can be reassigned and whether *p can be modified.

  1. char c = 'A'; char * const p = &c;
  2. int i = 100; const int * p = &i;
  3. float f = 3.14; float const * p = &f;
  4. long l = 123; const long * const p = &l;
Show answer
  1. char * const p: const pointer to char.

    • Can p be reassigned? No. (The pointer is const.)
    • Can *p be modified? Yes. (The char is not const.)
  2. const int * p: Pointer to const int.

    • Can p be reassigned? Yes. (The pointer is not const.)
    • Can *p be modified? No. (The int is const.)
  3. float const * p: Pointer to const float. (This is the same as const float * p.)

    • Can p be reassigned? Yes.
    • Can *p be modified? No.
  4. const long * const p: const pointer to const long.

    • Can p be reassigned? No.
    • Can *p be modified? No.

Conclusion

You've now learned the crucial difference between a pointer to const data and a const pointer. This isn't just a syntactic trick; it's a powerful tool for expressing intent and enforcing correctness, which is paramount in safety-critical software development.

Key Takeaways:

  • Pointer to const data (const int *p or int const *p): The data is read-only through this pointer. The pointer itself can be changed.
  • const Pointer (int * const p): The pointer itself is read-only and cannot be reassigned once initialized. The data it points to can be modified.
  • The Reading Rule: Read declarations from right to left to decipher them. const applies to what is on its left, unless it's at the start of the declaration.
  • Combined (const int * const p): Neither the pointer nor the data it points to can be changed.

Preview of the Next Lesson:
Now that we have the theoretical foundation, our next lesson will focus on the practical application of these concepts. We will learn how to apply const correctness to function parameters to create safer, more readable APIs. This will directly build on today's knowledge and show how const helps create robust interfaces, a skill directly applicable to your work with AUTOSAR components and communication stacks.