Constant Pointer vs Pointer to Constant in C — All Four const Combinations
A pointer that constantly points to the same address of its type throughout the program is known as a constant pointer, whereas a pointer that points to a constant (the value at that address can't be changed by this pointer) is termed a pointer to constant.
Let's understand it by an example, but first check out the prerequisite posts on Pointers in C, Double pointer in C, and Pointer vs Reference variable in C/C++.
Constant Pointer
As the name implies, a constant pointer constantly points to a fixed memory address of its type. It can't be re-assigned or modified to another address. In case the developer wants to do so, the compiler will raise an illegal operation error — but the value at that pointed address can be modified.
//Syntax
<type of pointer> *const <name of pointer>;
#include <stdio.h>
int main(void) {
//Declaration
int a = 10; //Variable
int * const b = &a; // Constant pointer
//Re-assigning
int c=15; //Another variable
b = & c; // Error, because constant pointer can't be modified
return 0;
}
prog.c: In function 'main': prog.c:11:3: error: assignment of read-only variable 'b' b = & c;
Pointer to constant
A pointer that constantly points to a fixed value at the memory address. We can't modify that value using this pointer, but we definitely can modify or re-assign the pointer to another address.
//Syntax
const <type of pointer> * <name of pointer>;
or
<type of pointer> const * <name of pointer>;
#include <stdio.h>
int main(void) {
//Declaration
int a = 10; //Variable
const int * b = &a; // Pointer to constant
//Re-assigning
int c=15; //Another variable
b = & c; //Re-assigned — allowed
*b = 25; // Error — value modification not allowed using pointer to constant
return 0;
}
Compilation error prog.c: In function 'main': prog.c:10:6: error: assignment of read-only location '*b' *b = 25;
Constant Pointer to constant
A pointer that neither can be modified or re-assigned, nor the pointed value can be modified, is termed as a constant pointer to constant.
//Syntax
const <type of pointer> * const <name of pointer>;
#include <stdio.h>
int main(void) {
//Declaration
int a = 10; //Variable
const int * const b = &a; // constant Pointer to constant
//Re-assigning
int c=15; //Another variable
b = & c; //Error — Re-assign not allowed
*b = 25; // Error — value modification not allowed
return 0;
}
prog.c: In function 'main':
prog.c:10:8: error: assignment of read-only variable 'b'
b = & c;
^
prog.c:11:8: error: assignment of read-only location '*b'
*b = 25;
Applications of Different kinds of pointers
When you're designing C programs for embedded systems, or special purpose programs that need to refer to the same memory (multi-processor applications sharing memory) then you need constant pointers.
Pointer to constant is commonly used while passing a pointer as a parameter where value modification is not allowed or value is read-only.
Constant pointer to constant is used, for example, for knowing the status of a specific interrupt through the pointer.
All Four const Combinations
The const keyword can appear in two positions relative to the *. Each position locks a different thing — the pointer itself, or the value it points at. The four combinations are:
| Declaration | Name | Pointer re-assignable? | Value modifiable? |
|---|---|---|---|
int *p |
Regular pointer | ✅ Yes | ✅ Yes |
const int *p |
Pointer to constant | ✅ Yes | ❌ No |
int *const p |
Constant pointer | ❌ No | ✅ Yes |
const int *const p |
Constant pointer to constant | ❌ No | ❌ No |
Reading trick: read the declaration right-to-left from the variable name. int *const p reads: "p is a const pointer to int." const int *p reads: "p is a pointer to const int."
Pointer to Constant — const int *p
The pointer itself can be moved to point at different addresses. The value through the pointer cannot be modified. This is the most common const usage — function parameters that receive data they must not alter.
#include <stdio.h>
int main(void) {
int x = 10, y = 20;
const int *p = &x; /* pointer to constant int */
printf("%d\n", *p); /* OK — reading is allowed */
// *p = 99; /* ERROR: assignment of read-only location */
p = &y; /* OK — pointer itself can change */
printf("%d\n", *p); /* 20 */
return 0;
}
Common in function signatures:
/* str is read-only inside the function — caller's data is safe */
size_t my_strlen(const char *str) {
size_t len = 0;
while (*str++) len++;
return len;
}
Constant Pointer — int *const p
The pointer is fixed to one address forever after initialisation. The value at that address can still be changed. Useful when a pointer must always refer to the same object — hardware register addresses, fixed buffers.
#include <stdio.h>
int main(void) {
int x = 10, y = 20;
int *const p = &x; /* constant pointer — must initialise now */
*p = 99; /* OK — value is modifiable */
printf("%d\n", x); /* 99 */
// p = &y; /* ERROR: assignment of read-only variable 'p' */
return 0;
}
Embedded use case — a pointer to a memory-mapped peripheral register that never moves:
/* UART data register always lives at 0x40013804 */
volatile uint32_t *const UART_DR = (volatile uint32_t *)0x40013804;
void uart_send(uint8_t byte) {
*UART_DR = byte; /* write to register — OK */
// UART_DR = ...; /* ERROR — cannot change the address */
}
Constant Pointer to Constant — const int *const p
Both the pointer and the value are locked. This is the most restrictive form — nothing about the pointer or its target can change after initialisation. Typically used for truly immutable data: string literals, lookup tables, ROM content.
#include <stdio.h>
int main(void) {
int x = 42;
const int *const p = &x;
printf("%d\n", *p); /* OK — reading allowed */
// *p = 99; /* ERROR — cannot modify value */
// p = &x; /* ERROR — cannot modify pointer */
return 0;
}
/* Lookup table — pointer never moves, values never change */
static const char *const day_names[] = {
"Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday", "Sunday"
};
const in Function Parameters
Function parameter const is the most important practical use. It documents intent, enables the compiler to catch mutations, and allows the function to accept both const and non-const arguments.
| Parameter | Accepts | Can modify value? | Can reseat pointer? |
|---|---|---|---|
int *p |
Non-const pointer only | ✅ | ✅ (local only) |
const int *p |
Both const and non-const | ❌ | ✅ (local only) |
int *const p |
Non-const pointer only | ✅ | ❌ |
const int *const p |
Both const and non-const | ❌ | ❌ |
void process(const int *data, size_t len) {
for (size_t i = 0; i < len; i++)
printf("%d ", data[i]); /* read-only access */
}
int arr[] = {1, 2, 3};
const int carr[] = {4, 5, 6};
process(arr, 3); /* OK — non-const passed to const param */
process(carr, 3); /* OK — const passed to const param */
const int * over int * for any input-only parameter. It widens what the function accepts, documents the contract, and lets the compiler optimise more aggressively.
Quick Reference
| Syntax | What's const | Read trick | Typical use |
|---|---|---|---|
int *p |
Nothing | p → pointer → int | General-purpose pointer |
const int *p |
Value at address | p → pointer → const int | Read-only function params, iterators |
int *const p |
Pointer address | p (const) → pointer → int | Fixed hardware register address |
const int *const p |
Both | p (const) → pointer → const int | Immutable lookup tables, ROM data |