Skip to main content

Welcome to a Grasp lesson

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

Lesson 11

Mastering `const` with Pointers

Recommended resources

Hello! Welcome to your next lesson.

In our last session, we saw how pass-by-pointer allows a function to modify a caller's variables, a powerful and essential technique in C. We used an example of populating a CAN_Message_t struct, where a function was given full write access to the caller's data.

However, in many real-world scenarios, especially in safety-critical systems like the ones you develop, we need a more controlled approach. We often want the efficiency of passing a pointer to avoid copying large data structures, but we also want to guarantee that the function cannot modify the data. This is where the const keyword becomes indispensable for creating robust and clear APIs.

Today's lesson will focus on exactly that. We will apply const to pointers, distinguishing between a constant pointer, a pointer to a constant, and a constant pointer to a constant. Mastering this is crucial for both interviews and for writing safe, professional C code.

A Rule for Deciphering Declarations

Before diving into the variations, let's learn a simple and reliable technique for reading C declarations, often called the "right-to-left" or "spiral" rule. It's invaluable for quickly parsing complex pointer declarations under pressure.

constant pointer vs pointer on a constant value - Stack Overflow

The following resource introduces a simple rule for parsing C declarations. This will be our primary tool for understanding the different placements of const.

Read the answer starting with 'To parse complicated types...'. It introduces the right-to-left rule. Then, read the answer that begins 'How to read it? Follow the below simple step...' which applies this rule step-by-step. Focus on the method, not just the result.

The key takeaway is to start from the variable name and read outwards, applying * as "pointer to" and const as "constant". Let's use this rule to break down the three main scenarios.


Case 1: Pointer to a Constant

This is the most common use of const with pointers in function arguments. It is a promise that the function will not modify the data pointed to.

Declaration:
const int *p;
or equivalently
int const *p;

Reading it (right-to-left):

  • p is a...
  • * ...pointer to an...
  • int ...integer that is...
  • const ...constant.

So, p is a pointer to a constant integer.

Implications:

  • You cannot change the value at the address p points to (i.e., *p = 10; is illegal).
  • You can change the pointer itself to point to a different address (i.e., p = &some_other_int; is legal).

This is the mechanism you'd use to pass a large configuration struct to a function for reading without allowing any modifications.

Let's examine some code snippets to solidify this.

Important Question Related to const pointer in C - Aticleworld

These examples from Aticleworld demonstrate the behavior of a pointer to a constant. Pay attention to which operations succeed and which fail.

Analyze 'Question 3' and 'Question 4'. In Question 3, notice that incrementing the pointer (pcName++) is allowed. In Question 4, notice that attempting to modify the data through the pointer (pcName[0] = 'A') results in a compilation error.

An important subtlety: const char *p means the data is constant as far as p is concerned. If the original data was not declared const, it can still be modified through other non-const pointers or directly by its variable name.


Case 2: Constant Pointer

This type of pointer is less common in function parameters but is very useful for representing fixed hardware addresses.

Declaration:
int * const p;

Reading it (right-to-left):

  • p is a...
  • const ...constant...
  • * ...pointer to an...
  • int ...integer.

So, p is a constant pointer to an integer. Because const directly follows the variable name, it applies to the pointer itself.

Implications:

  • You can change the value at the address p points to (i.e., *p = 10; is legal).
  • You cannot change the pointer to point to a different address (i.e., p = &some_other_int; is illegal). A const pointer must be initialized at declaration and cannot be reassigned.

This is ideal for modeling a memory-mapped hardware register, where the address is fixed, but you need to read from or write to it. For example:
volatile uint32_t * const UART0_TX_REG = (uint32_t *)0x4000C000;

Let's see the compiler error this produces.

Important Question Related to const pointer in C - Aticleworld

This example shows what happens when you try to modify a constant pointer.

Examine 'Question 5'. The code attempts to increment the pointer itself, which is declared as char * const. As the explanation notes, this is illegal and results in a compilation error.


Case 3: Constant Pointer to a Constant

This is the most restrictive form, combining the limitations of the previous two cases.

Declaration:
const int * const p;

Reading it (right-to-left):

  • p is a...
  • const ...constant...
  • * ...pointer to an...
  • int ...integer that is...
  • const ...constant.

So, p is a constant pointer to a constant integer.

Implications:

  • You cannot change the value at the address p points to (*p = 10; is illegal).
  • You cannot change the pointer to point to a different address (p = &some_other_int; is illegal).

This is useful for a fixed pointer to a piece of read-only data, such as a pointer to a specific entry in a constant calibration table stored in ROM.

The compiler will prevent any modification of the pointer or the data it points to.

Important Question Related to const pointer in C - Aticleworld

This final example from Aticleworld demonstrates the doubly-restrictive nature of a constant pointer to a constant.

Look at 'Question 6'. The declaration const char *const pcName makes both the pointer and the data it points to immutable. The code attempts to increment the pointer, which rightly fails.


Summary and Practice

The different combinations can be confusing, but they follow a consistent logic. This table provides a quick visual summary.

Understanding `const` with Pointers in C
This table summarizes what can be changed for each of the three main `const` pointer declarations. 'Value' refers to the data the pointer points to, and 'Pointer' refers to the address stored in the pointer variable itself.

Now, let's put it all together. The following resource provides a single function that uses all three types of const pointers, plus a non-const pointer. Before looking at the comments in the code, try to predict which lines are legal and which will cause a compilation error.

constant pointer vs pointer on a constant value - Stack Overflow

This code snippet from a Stack Overflow answer is an excellent exercise. It puts all the pointer types we've discussed side-by-side in a single function.

Focus on the code within the Foo function. For each of the four parameters (ptr, ptrToConst, constPtr, constPtrToConst), analyze the two operations performed on it. Predict whether each line will compile or result in an error based on the rules we've just covered. Then, read the comments to check your predictions.


Conclusion

You have now analyzed the critical role of const in pointer declarations, a topic that frequently appears in C programming interviews. Understanding these distinctions is a hallmark of a programmer who thinks carefully about API design, efficiency, and safety.

Key Takeaways:

  • The right-to-left rule is a reliable method for deciphering complex C declarations.
  • Pointer to const (const int *p): You can change the pointer, but not the data it points to. Use this for efficient, read-only access to data in function calls.
  • const Pointer (int * const p): You can change the data, but not the pointer itself. Use this for fixed hardware addresses.
  • const Pointer to const (const int * const p): You can change neither the pointer nor the data. Use this for fixed pointers to read-only configuration data.

In our next lesson, we will investigate a common and dangerous pointer-related bug. We will predict the behavior of code that returns a pointer to a local (stack) variable and explain the resulting undefined behavior. This will tie together our knowledge of pointers with the program's memory layout and function call stack.