const and restrict
const tells the compiler that an object will not be written through this name, and it enforces that at compile time. The keyword does not make anything read-only at run time and does not stop another name from writing the same object. restrict goes further and promises that two pointers do not overlap, which lets the optimiser generate better code.
const objects
A const object needs an initialiser, because there is no later chance to assign it. Its name can be read anywhere in scope and cannot be the target of an assignment. A const array of characters behaves like a string that the program promises not to change.
#include <stdio.h>
static const int limit = 10;
static const char name[] = "fixed";
int main(void)
{
const int local = 5;
printf("limit: %d\n", limit);
printf("local: %d\n", local);
printf("name: %s\n", name);
printf("sizeof name: %zu\n", sizeof name);
return 0;
}
gcc -Wall -Wextra const.c -o const
./const
limit: 10
local: 5
name: fixed
sizeof name: 6
The size includes the terminator, as lesson 23 explained. A const local lives on the stack like any automatic variable; the qualifier removes the permission to write, not the storage.
Pointer to const, const pointer
The star decides what is const. In const int *reader the pointed-to int is const, so the pointer may move but the value cannot be written through it. In int *const writer the pointer itself is const, so the address cannot change but the value can.
#include <stdio.h>
int main(void)
{
int value = 5;
const int *reader = &value;
int *const writer = &value;
printf("through reader: %d\n", *reader);
*writer = 9;
printf("through writer: %d\n", *writer);
printf("value: %d\n", value);
return 0;
}
gcc -Wall -Wextra pointers.c -o pointers
./pointers
through reader: 5
through writer: 9
value: 9
The reader sees the new value even though its target is const, because const is a property of the access path, not of the object. Reading the declaration right to left is the reliable habit: pointer to const int, and const pointer to int.
const parameters
A function that only reads an array should say so: the parameter is const int *, the array is not modified, and the caller can pass a const array without casting away anything. The compiler checks the body and refuses an assignment through the parameter.
#include <stdio.h>
static int sum(const int *values, int count)
{
int total = 0;
for (int i = 0; i < count; i++) {
total += values[i];
}
return total;
}
int main(void)
{
int values[4] = {1, 2, 3, 4};
printf("sum: %d\n", sum(values, 4));
printf("first: %d\n", values[0]);
return 0;
}
gcc -Wall -Wextra param.c -o param
./param
sum: 10
first: 1
The first element is unchanged, which the const keyword does not prove by itself but the compiler enforces inside the function. The count is passed separately because a pointer does not carry the length, the same interface as lesson 28.
The restrict promise
restrict on a pointer parameter promises that the object is not reached through any other pointer in the same scope. The compiler may then keep values in registers across the loop instead of reloading them. The promise is not checked, and breaking it is undefined behaviour.
#include <stdio.h>
static int dot(const int *restrict a, const int *restrict b, int count)
{
int total = 0;
for (int i = 0; i < count; i++) {
total += a[i] * b[i];
}
return total;
}
int main(void)
{
int left[3] = {1, 2, 3};
int right[3] = {4, 5, 6};
printf("dot: %d\n", dot(left, right, 3));
return 0;
}
gcc -Wall -Wextra restrict.c -o restrict
./restrict
dot: 32
1×4 + 2×5 + 3×6 is 32. The two arrays are separate objects, so the promise holds. Calling dot(left, left, 3) would break it, and the result would be whatever the optimiser did with the aliasing.
Writing through a const pointer
The compiler refuses a store through a pointer to const, and the message names the dereferenced expression. The object itself is not const, which is why another pointer can still change it, but this access path may not:
#include <stdio.h>
int main(void)
{
int value = 5;
const int *reader = &value;
*reader = 9;
printf("%d\n", *reader);
return 0;
}
gcc -Wall -Wextra readonly.c -o readonly
readonly.c: In function 'main':
readonly.c:8:13: error: assignment of read-only location '*reader'
8 | *reader = 9;
| ^
The second mistake goes the other way: copying a pointer to const into a plain pointer would drop the promise. GCC warns about the discarded qualifier, and the write that follows would be undefined if the object really were const:
#include <stdio.h>
static int read(const int *value)
{
return *value;
}
int main(void)
{
int value = 5;
const int *reader = &value;
int *writer = reader;
printf("%d\n", read(writer));
return 0;
}
gcc -Wall -Wextra discard.c -o discard
discard.c: In function 'main':
discard.c:12:19: warning: initialization discards 'const' qualifier from pointer target type [-Wdiscarded-qualifiers]
12 | int *writer = reader;
| ^~~~~~
The warning is the compiler asking whether the const was a mistake. If the program really owns a writable object, keep a separate plain pointer from the start; if it does not, keep the const and the write disappears from the code.
Exercises
- Declare a const array of five temperatures and a function that returns the average without changing it.
- Write both pointer declarations for the same int and explain in one sentence what each one allows.
- Add restrict to a function that copies one array to another and call it with overlapping ranges. Describe what the program is allowed to do.
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
Why must a
constobject have an initialiser?Answer
Because it cannot be assigned later
The qualifier forbids assignment through that name, so the only chance to give it a value is the declaration.
-
What does
const int *readerallow?Answer
Changing the pointer but not the int through it
The pointed-to int is const, while the pointer itself can be moved to another object.
-
What does
int *const writerallow?Answer
Changing the int but not the pointer
The pointer is const, so its address cannot change, but the int it points to is writable.
-
What does
restrictpromise to the compiler?Answer
That no other pointer reaches it
The promise allows the optimiser to keep values in registers, and breaking it is undefined behaviour.
-
What does this program print?
#include <stdio.h> int main(void) { const char name[] = "fixed"; printf("%zu\n", sizeof name); return 0; }Answer
6
Five visible characters plus the terminating zero make six bytes.
-
What does this program print?
#include <stdio.h> int main(void) { int value = 5; const int *reader = &value; int *const writer = &value; printf("through reader: %d\n", *reader); *writer = 9; printf("through writer: %d\n", *writer); printf("value: %d\n", value); return 0; }Answer
through reader: 5 / through writer: 9 / value: 9
The writer pointer changes the object, and the reader sees 9 even though its access path is a pointer to const.
-
What does this program print?
#include <stdio.h> static int sum(const int *values, int count) { int total = 0; for (int i = 0; i < count; i++) total += values[i]; return total; } int main(void) { int values[4] = {1, 2, 3, 4}; printf("sum: %d\n", sum(values, 4)); printf("first: %d\n", values[0]); return 0; }Answer
sum: 10 / first: 1
The function adds 1, 2, 3 and 4 through a pointer to const, and the array is not modified.
-
What does this program print?
#include <stdio.h> static int dot(const int *restrict a, const int *restrict b, int count) { int total = 0; for (int i = 0; i < count; i++) total += a[i] * b[i]; return total; } int main(void) { int left[3] = {1, 2, 3}; int right[3] = {4, 5, 6}; printf("%d\n", dot(left, right, 3)); return 0; }Answer
32
1 times 4 plus 2 times 5 plus 3 times 6 is 32, and the two arrays do not overlap.
-
This program is built with
gcc -std=c17 -Wall -Wextra readonly.c -o readonly. What does GCC report?#include <stdio.h> int main(void) { int value = 5; const int *reader = &value; *reader = 9; printf("%d\n", *reader); return 0; }Answer
error: assignment of read-only location '*reader'
The access path forbids the store, so the compiler rejects the assignment and names the dereferenced expression.
-
This program is built with
gcc -std=c17 -Wall -Wextra discard.c -o discard. Which warning flag does GCC print?#include <stdio.h> int main(void) { int value = 5; const int *reader = &value; int *writer = reader; printf("%d\n", *writer); return 0; }Answer
-Wdiscarded-qualifiers
Copying a pointer to const into a plain pointer discards the qualifier, which
-Wdiscarded-qualifiersreports.
The code on this page was run with gcc (Alpine 15.2.0) 15.2.0 on Alpine Linux 3.24.3 (x86_64) in a Docker container.
Article author: Arthur Isaev