Another Look at CONST in C

Ju Hong Kim · October 4, 2020

Report a bug

Recently, I’ve been learning embedded programming from udemy and there was a section dedicated const. The section was interesting because despite programming in C a lot during my duration of my undergrad, I have never taken a look at const in much detail. I am sure there is more to const than what the course provides but I still learned a lot from this section itself.

const is a type qualifier that is often used in C and C++ programming, especially appearing in the standard libraries frequently. An example is the function definition of strlen:

       size_t strlen(const char *s);

The main purpose of const keyword is to document to programmers and compilers that the object cannot be mutated after initialization. A type qualifier is simply just giving additional information about the object to both the programmer and the compiler. Compilers can parse const to perform various optimization and it may place the object to ROM (Read Only Memory) since modifications are not supposed to be possible anyways. However, do not bet on your const objects to be stored in ROM (I believe it’s usually just stored on the stack anyways unless it’s scope is global).

Let’s look at a simple example of const before I delve into what I learned from the course:

const int x = 10;
x = 20;

Example 1: Illegal assignment to a const int

If we were to compile the code above, we would get the following error:

test.c: In function ‘main’:
test.c:6:4: error: assignment of read-only variable ‘x’
  x = 20;
    ^

As expected, the compiler would stop the programmer from changing the value of x since its qualified as a const. So far, everything I said should be familiar to any C programmer. But please bear with a bit longer.

How do we declare a variable to be a const? In the example above you would assume that the proper way is to declare the qualifier before the type, but it doesn’t matter where you place const in this example. const int x = 10; can also be written as int const x = 10; It’s all a matter of preference on how you read the line. For instance, const int x = 10; can be read as “x is a type int that is a const” and the latter can be read as “x is a const of type int”.

What about pointers? How does const work with pointers? Let’s see an example using the definition of strlen. const char *s means that the function will not modify the string you pass into the function. Let’s take a look at a simple implementation of strlen I made up on the spot:

size_t strlen(const char *s) {
        size_t size = 0;
        for (;*s; ++s) {
                size++;
        }
        return size;
}

Example 2: A simple implementation of strlen

As you can see from this implementation, you can modify locally the value of str to traverse through the string. This implementation does not stop you from incrementing the pointer of the string you pass into the function as long as you don’t modify the contents of what str is pointing to. We can see this with another example, where we point const char *s to a totally different area in memory than the one we passed into the function.

#include <string.h>
#include <stdio.h>
int puts_f(const char *s) {
        char *s2 = "baka";
        s = s2;
        printf("%s\n", s);
}

int main () {
        char s[5];
        strncpy(s, "zaku", 4);
        s[4] = '\0';
        puts_f(s);
}

Example 3: Changing the value of a pointer of the declaration const char *

The output of example 3 would be baka instead of the string, zaku, which we passed into the function puts_f. This example is another illustration how const char *s only qualifies char to be non-mutable but not the pointer s itself.

However, this does not mean a function with the argument const char *s cannot modify the string you are pointing to. As I said const qualifier is just for documentation purpose and your compiler will try to prevent any modification. You can explicitly remove the const qualifier when you assign its value to another pointer. With the new pointer, you can modify the original string as seen in example 4:

#include <stdio.h>
#include <string.h>

size_t strlen_f(const char *str) {
        size_t size = 0;
        char *s = (char *) str;
        s[0] = 'h';
        for (;*str; ++str) {
                size++;
        }
        return size;
}

int main() {
        char s[5];
        strncpy(s, "zaku", 4);
        s[4] = '\0';
        printf("the length of %s is ", s);
        printf("%d\n", strlen_f(s));
        printf("string after calling strlen_f: %s\n", s);
        return 0;
}  

Example 4: Modifying the string passed into the function as const char *

The above code is an example of being able to mutate the string you passed into a function as const char *str as seen with the output below:

the length of zaku is 4
string after calling strlen_f: haku

So what happens if we swap the order of the const?

As I said in my example with const int x, that the order of the qualifier does not matter. However, when dealing with pointers, it can get a bit confusing. Like before where const int x and int const x are the same, const char *s and char const *s are equivalent. But the following statements below mean very different things.

const int *s;
int *const s;
const int *const s;

We’ve already seen in our example 4 that const char *s does not allow the pointer s make any modifications but does not stop any other pointers from modifying whatever s is pointing to. We also learned from example 3 that s can point to different areas of memory as long as it does not attempt to mutate the values of the memory it points to. The same applies to any other data type such as const int *s. We will be exploring the behaviors of const using the data type int instead of char for simplicity sakes but the logic is the same.

So what is the difference between int const *s and int *const s? When reading a variable declaration, you can read the statement from right to left. For instance, int const *s can be read as s is a pointer to a const of type int. Similarly, we can read int *const s as s is a const pointer to a int. See the difference?

The major difference is the placement of const between the pointer * (it’s only a pointer symbol during variable declaration. Anywhere else, it’s a dereference operator. Weird isn’t it). The first declaration means s is pointing to some int that is non-mutable. The latter refers that the pointer s is non-mutable but the value in memory is still mutable. I’ll present two examples to make this clear.

int *const s = 1;
int *s2 = 2;
s = s2;

Example 5: Attempt to make an illegal assignment of int *const s to another pointer:

If we were to compile this, we would get the following error:

test3.c: In function ‘main’:
test3.c:7:4: error: assignment of read-only variable ‘s’
  s = s2;
    ^

In example 3, we saw that we could change the address where s points to. So why can we no longer do that when we change the declaration of s to int *const s? As I said before, s is no longer a pointer pointing to somewhere that is “write-protected”. With the new declaration, the address where s points to is “write-protected” while the data is not as seen below:

int *const s = (int *)malloc(sizeof(int));
*s = 1;
printf("before modification: %d\n", *s);
*s = 2;
printf("after modification: %d\n", *s);

Example 6: Changing the value pointed by int *const:

Output:

before modification: 1
after modification: 2

Example 6 illustrates that modifications are allowed to variables declared as int *const as long as the pointer is not changed. Looking at all these examples I have provided so far, you should be able to know the behavior of the declaration: const int *const s

Following the behavior of const int *s and int *const s, const int *const s means that both the pointer and the memory it is pointing to is non-mutable. So we cannot make any modification of any kind to s.

Summary

  • the order where you place const matters when dealing with pointers
  • const int *x - x is a pointer to a const int
  • int *const x - x is a const pointer to an int
  • const int *const x - x is a const pointer to a const int
  • Compiler will try to prevent writes to a const but there are ways to forgo the protection
  • const serves mainly for documentation purposes to the developers and the compiler
  • compiler can try to use the additional information for optimization

Twitter, Facebook