Skip to content
VLSI Mentor

C Part I · Module 7 · Pointers

const and Pointers

Two chapters in this module have already used a qualifier without explaining it. Chapter 7.7's sum took a const int * and the text said only that it meant the function would not write through the pointer, with the real treatment deferred here. That description was true and incomplete, because a pointer declaration involves two separate things that could be constant — the object at the far end, and the pointer itself — and C lets you mark either, both, or neither. The two single-marked forms are written almost identically, forbid opposite operations, and are confused constantly. There is a mechanical rule for reading them that always works, and once you can read them there is a design habit worth adopting, because one of these forms costs you nothing and makes every signature that uses it more honest.

Intermediate17 min readCPart IFundamentalsPointers

Part I · Module 7 · Chapter 7.11 · const and Pointers

1. Two Things That Could Be Constant

Chapter 7.1 established that a pointer declaration involves two objects: the pointer, and the thing it points at. Chapter 2.11 established that const on an object means this object must not be modified.

Put those together and there is an obvious question: when you write const in a pointer declaration, which of the two does it apply to?

The answer is that C lets you choose, and the choice is made by where you put the word:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    const int *p;        /* the int is const — p may be retargeted */
    int *const q;        /* the pointer is const — the int may be changed */
    const int *const r;  /* both */

2. The Reading Rule

One mechanical procedure handles every case:

Start at the name and read right to left.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
const int *p
          p          p
         *p          is a pointer
     int             to an int
const                that is const
                     -> p is a pointer to a const int

int *const q
           q         q
     const q         is a const
         * q         pointer
int                  to an int
                     -> q is a const pointer to an int

This is Chapter 7.9 §3's name-outward technique with const included. The only thing to remember is that const binds to what is on its left, unless there is nothing on its left, in which case it binds to the right. That is why const int * and int const * mean the same thing — in the first, const has nothing to its left so it attaches to int.

Some people write int const *p precisely because it makes the rule uniform. Both spellings are common; this curriculum uses const int *, which is far more widespread.

3. What Each One Forbids

Descriptions are forgettable. What makes these stick is knowing which operation each form rejects, and the compiler states it exactly.

const int *p — the object is read-only

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int a = 1, b = 2;
    const int *ptr_to_const = &a;

    *ptr_to_const = 5;          /* rejected */
    ptr_to_const = &b;          /* fine */
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
error: read-only variable is not assignable

You may not write through it. You may aim it somewhere else.

int *const q — the pointer is read-only

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int a = 1, b = 2;
    int *const const_ptr = &a;

    *const_ptr = 5;             /* fine */
    const_ptr = &b;             /* rejected */
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
error: cannot assign to variable 'const_ptr' with const-qualified type 'int *const'

You may write through it. You may not aim it somewhere else.

const int *const r — both

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    const int *const both = &a;

    *both = 5;                  /* rejected */
    both = &b;                  /* rejected */
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
error: read-only variable is not assignable
error: cannot assign to variable 'both' with const-qualified type 'const int *const'
Declaration*p = xp = &ySay it as
int *pallowedalloweda pointer to an int
const int *prejectedalloweda pointer to a const int
int *const pallowedrejecteda const pointer to an int
const int *const prejectedrejecteda const pointer to a const int

Note that a const pointer must be initialised at its declaration — like any const object, Chapter 4.2 §4 established that there is no later opportunity to give it a value.

4. Which One You Will Actually Write

The three forms are not equally useful, and it is worth being direct about the ratio.

const int * is the one that matters. It appears constantly, in almost every function that reads data it was given. Everything in §5 is about it.

int *const is rare. It says this pointer will never be aimed anywhere else, which is a statement about a local variable's use rather than about an interface, and a reader can usually see that from the code. It is occasionally useful in Part II, where a pointer to a fixed hardware location genuinely never changes.

const int *const is rarer still, and is mostly seen where both properties are being documented deliberately.

5. Const-Correctness in Practice

Azvya Education Pvt. Ltd.VLSI Mentor
two functions, two honest signatures
#include <stdio.h>
#include <stddef.h>

int sum(const int *values, size_t count)
{
    int total = 0;
    for (size_t i = 0; i < count; i++) {
        total = total + values[i];
    }
    return total;
}

void scale(int *values, size_t count, int factor)
{
    for (size_t i = 0; i < count; i++) {
        values[i] = values[i] * factor;
    }
}

int main(void)
{
    int readings[4] = {1, 2, 3, 4};
    size_t n = sizeof readings / sizeof readings[0];

    printf("sum before   %d\n", sum(readings, n));
    scale(readings, n, 3);
    printf("sum after    %d\n", sum(readings, n));

    return 0;
}

Output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sum before   10
sum after    30

A reader scanning only the two signatures learns something real: sum will not change your data, and scale will. Chapter 7.5 §3 made this point in a table and could not demonstrate it; this is the demonstration.

That is the practical content of const-correctness: the qualifiers are not decoration, they are the part of a signature that says which direction the data flows. Chapter 5.9's contract idea is the right frame — const moves one line of the contract out of a comment and into something the compiler checks.

What const does and does not promise

Two limits are worth stating so the guarantee is not over-read.

const is a promise about this access path, not about the object. If a function receives a const int *, it may not write through that pointer. It does not follow that the object cannot change — the caller still has a non-const name for it, and may change it before or after the call.

const is not a security boundary. A cast can remove it, and writing through a pointer that has had const cast away is undefined behaviour if the object itself was declared const. The qualifier exists to catch mistakes and to document intent, which is a large practical benefit and a different thing from enforcement against a determined caller.

6. Where You Will Meet It Most

The single most common const pointer in all of C is one this curriculum has not reached: const char *, pointing at text that must not be modified.

The reason is a rule about string literals that belongs to Chapter 8.2, and it is the reason library functions that take text almost always declare it const char *. When you meet those signatures, they are this chapter's first form with char in place of int, and the reading rule is identical.

7. What You Should Be Able to Say Now

  1. A pointer declaration has two things that could be const: the pointer, and the object.
  2. Read right to left from the name — and const binds to its left unless there is nothing there.
  3. const int *p — the object is read-only: *p = x rejected, p = &y allowed.
  4. int *const p — the pointer is read-only: *p = x allowed, p = &y rejected.
  5. const int *const p — both rejected.
  6. int const * and const int * mean the same thing.
  7. A const pointer must be initialised at its declaration.
  8. const on a parameter restricts the function, not callers — it accepts both const and non-const arguments.
  9. A non-const parameter rejects const data, which is the asymmetry that makes marking it free.
  10. const documents direction of data flow and is checked — Chapter 5.9's contract, enforced.

8. Exercises

Exercise 1 — read them aloud. Say in words what each declares.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
const double *a;
double *const b;
const double *const c;
char const *d;

Exercise 2 — predict the rejections. For each line, say whether it compiles, given int x = 1, y = 2;.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
const int *p = &x;   *p = 5;    p = &y;
int *const q = &x;   *q = 5;    q = &y;
const int *const r = &x;  *r = 5;   r = &y;
int *s = &x;         *s = 5;    s = &y;

Exercise 3 — place the const. Write the declaration for each description.

  • a pointer to an int that must not be written through
  • a pointer to an int that must always aim at the same object
  • a pointer that can neither be retargeted nor written through
  • an ordinary pointer to int

Exercise 4 — fix the signatures. For each, say whether the parameter should be const and rewrite it if so.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int  average(int *values, size_t n);
void zero_all(int *values, size_t n);
int  count_matching(int *values, size_t n, int wanted);
void swap(int *a, int *b);

Exercise 5 — explain the asymmetry. sum(readings, 4) compiles with readings non-const, and scale(fixed, 4, 3) does not with fixed const. Explain both in one sentence each, and say which function's author made their code harder to reuse.

Exercise 6 — find the over-read. A colleague says "I passed it as const int *, so the data cannot change during the call." Say under what circumstances they are right, under what circumstances they are wrong, and what const actually promises.

Exercise 7 — initialise it. Explain why int *const p; without an initialiser is a problem, and name the earlier chapter that established the same rule for ordinary const objects.

Exercise 8 — audit a module. Take the four functions you wrote or read in Chapter 7.7's exercises and give each an honest signature with the right qualifiers. For each one, write the single sentence a caller learns from the signature alone.

9. Summary

  • A pointer declaration has two candidates for const: the pointer and the pointed-at object.
  • Read right to left from the name; const binds leftward unless nothing is there.
  • const int *p forbids *p = x; int *const p forbids p = &y; both together forbid both.
  • int const * and const int * are the same.
  • A const pointer must be initialised where it is declared.
  • const on a parameter restricts the function and accepts every argument — it is free.
  • A non-const parameter rejects const data, so omitting const is what costs you callers.
  • It documents data-flow direction and is compiler-checked — Chapter 5.9's contract made real.
  • const char * is the form you will meet most, and it is Chapter 8.2's reason.

Every pointer type in this module has named the thing it points at — int *, double *, int **. That is what makes dereferencing meaningful, as Chapter 7.3 established with two pointers holding the same address and reading different widths. But some interfaces genuinely cannot know what they are handling: a routine that copies bytes from one place to another, or that compares two items it has been told nothing about, should work for every type rather than one. C has a pointer type that deliberately drops the second half of Chapter 7.3's definition, and the next chapter covers what you gain, and the precise price you pay.

Standards & specifications

Governing standard
ISO/IEC 9899 (the C language standard)(opens ISO/IEC (JTC 1/SC 22/WG 14) in a new tab)

Defines the C language itself — the type system, expression semantics, object model, storage duration and the behaviour categories a conforming implementation must honour. It is a FAMILY of editions (C89/C90, C99, C11, C17, C23) and this curriculum names the edition wherever a claim depends on one. It does not define any foreign-function interface to a hardware description language, and it does not define platform behaviour: what a memory-mapped access does on real silicon is the implementation's and the device's contract, not this standard's. Working drafts published by WG14 are freely available and are what most engineers read day to day; a draft is not the published standard.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the C Programming curriculum.