Const Pointers and Pointers to Const Values in C

In C, the syntax and semantics of pointers that are const and pointers to const values can be a bit confusing.

Constants

Constants can be used like variables but it is only possible to read from them. Changing their initial value is not allowed.

This is where things are still simple. So let’s start with ordinary constants:

#include <stdio.h>

int main(void)
{
    const int number = 42;
    const char str[] = "Hello World";

    // It is not allowed to change the value of a const variable:
    //number = 5; // Compile Error!!!

    // Same here:
    //str[5] = 'N'; // Compile Error!!!

    printf("Number: %d\n", number);
    printf("Str: %s\n", str);
}

As you can see here you can just take a normal variable declaration with an initializer (e.g. int number = 42) and turn it into a constant by putting the keyword const in front of the declaration (e.g. const int number = 42).

Whenever you try to change the value of this constant, though, you will get a compile error.

Of course, this also works for more complex variables like character arrays or structs. Try to change one of their members, and the compiler will inform you that this operation is not allowed.

Const Pointers

A const pointer is a pointer that points to one specific address in memory and this address cannot be changed once the pointer has been initialized. It is basically no different from the constants we looked at before. It is just a pointer that happens to be a constant.

Example:

#include <stdio.h>

int main(void)
{
    int num1 = 42;
    int num2 = 5;

    int* const numptr = &num1;

    // You can modify the value at the address a const pointer points to:
    *numptr = 21;

    // But not the address the pointer variable points to:
    //numptr = &num2; // Compile Error!!!

    printf("Number: %d\n", num1);
}

Output:

Number: 21

As you can see here the syntax is a bit confusing. We declare a const pointer that points to the address of the int variable num1 with int* const numptr = &num1.

Instead of putting the keyword const before our declaration we have to put it after the pointer part of the declaration (int*) and before the name of our constant (numptr).

In the code we use this pointer to change the value of the pointee num1 which works flawlessly as we can see when we print it with printf at the end of the program.

If we tried to make numptr point to num2 the program would not compile and the compiler would complain that numptr is a constant.

So with a const pointer we can change the value it points to but we cannot change the memory address it points to.

Pointers to Const Values

With a pointer to a const value it is the other way around. The pointer can point to a non-const variable but you cannot change the value of this pointee via the pointer. Changing the address the pointer points to is no problem, though.

Let’s try to make this clearer with an example:

#include <stdio.h>

int main(void)
{
    int num1 = 42;
    int num2 = 5;

    const int *numptr = &num1;
    const char *strptr = str;

    // You can modify the address a pointer to a const value points to:
    numptr = &num2;

    // But you can't use the pointer to change the
    // value at the address it points to:
    //*numptr = 5; // Compile Error!!!

    printf("Number: %d\n", *numptr);
    printf("Str: %s\n", strptr);
}

Output:

Number: 5

As you can see here the declaration of a pointer to a const value looks similar to the declaration of an ordinary constant. We just put the keyword const in front of a normal pointer declaration (e.g. const int *numptr = &num1) with an initializer.

We can change the pointer to point to the address of num2 instead of the initial num1 one but if we tried to change the value at this address we would get a compile error.

Pointers that allow anybody who hold them to read the data that they point to but not to change it are very useful in practice. They allow us to pass a pointer to some data to a function and the function can only read the data. We can even see from the prototype of the function that it will not harm our data. Using such pointers makes our code a lot safer whenever read-only access is all that is needed.

Pointer Syntax

The syntax can be summarized as follows:

Const Pointer Syntax in C

Examples:

Const PointerPointer to Const Value
int* const numptr;const int* numptr;
char* const numptr;const char* strptr;
struct SomeStruct* const structptr;const struct SomeStruct* structptr;
Info

In case you are wondering: int* varname and int *varname are the same in C. For a const pointer it is mandatory to use the first style, though.

How to use them in Practice

Const Pointers

There are not many cases where you will need const pointers. Especially in user mode programs you won’t see them very often, if at all. They can be useful in close to the metal programming, though.

Let’s say you work on an embedded platform or write a device driver and you have to interact with graphics RAM that is mapped to a fixed address in memory like 0x0000FFFF and you want to address each byte individually. In this case you could declare a global const pointer and put it in a header file like e.g. gpu.h:

...
uint8_t* const graphics_ram = 0x0000FFFF;
...

Since graphics_ram is a const pointer you can’t change the address by accident but you can use the pointer to read and write each byte of your graphics ram.

Pointers to Const Values

It is a completely different story with pointers to const values, tough. They allow you to write functions that take pointers to user controlled data and tell the user and the compiler that you read the data pointed to but have no intention to change it. The compiler will then enforce this contract you made with the user (unless you cast the const away but that is very bad practice).

Pointers to const values are a great tool to improve the safety of your code and they are so useful that they are used all over the standard library.

Some examples:

Standard Library FunctionPrototype
strlensize_t strlen(const char *s);
strdupchar *strdup(const char *s);
memcpyvoid *memcpy(void *dest, const void *src, size_t n);
memmovevoid *memmove(void *dest, const void *src, size_t n);
qsortvoid qsort(void base, size_t nmemb, size_t size, int (compar)(const void *, const void *));

Leave a comment