Why do we use `const` for strings when defining with pointers in C?

Viewed 3522

For defining arrays with pointers, we write
int *const a;
because arrays have variable values but constant pointers. But for strings, we write
const char *s;.

I don't understand why. Is this correct? Do strings really have constant values and variable pointers in C?

3 Answers

Consider the following:

char* a;
char* const b = "Hello"
const char* c;
const char* const d = "world";

a just points to a character. That might be the first character of a string, or a single byte. That character can be modified by writing *a='Z' or a[0]='q' or by passing a to a function taking a char* parameter. The pointer itself can also be modified to point at some other characters, e.g. a=b; or a="foo";. This is the most flexible form, and the least safe, because you can do anything with it.

b is a constant pointer to a character. In this case, it's pointing at an array of characters. Those characters can be modified, e.g. b[1]='a'; or *b=0; but b itself is constant, meaning that it cannot ever point at a different part of memory. This form may be used when you have allocated a buffer or array whose contents you want to modify, but which cannot move, since it was allocated by the operating system.

c points to a constant character. You can change what it points at (c="foo"; or c=b;) but you cannot use this pointer to change the characters that it points at -- even if it's pointing at the non-constant buffer b. This form is commonly used when handling pre-generated strings. You can use this variable to store strings you've found, and pass their address around, as long as you don't change the strings themselves.

d is a constant pointer to a constant character. You can't change the characters it's pointing at, and you can't point the pointer itself at a different memory location. All you can do is read the string or individual characters (char e=d[4]; or puts(d);). This form is mainly used when passing around reference strings, for example when naming real objects.

Code such as int *const a; is nonsense in most use-cases. The main use of making a pointer const is when you have a pointer-based look-up table. Or when you want a pointer to be stored in read-only memory on an embedded system.

Some confused programmers like to write code like void func (int* const ptr) but this is only obfuscation, in my opinion, since ptr is a copy of the original pointer anyhow, and the caller couldn't care less what the function does with its local copy internally.

Not to be mixed up with const correctness, which means that pointers to read-only data should be declared as const int* ptr. This is widely recognized as good programming practice, and it is canonical C, since most of the C standard utilizes const correctness for the C standard library.

As for pointers to string literals (like "hello") specifically, they should be declared const char* ptr = "hello"; for the reason that modifying a string literal invokes undefined behavior. Meaning that modifying it is a bug and might cause a program crash. This is a known flaw in C - the type of the string literal itself, even though we aren't allowed to write to it, is char[]. This is for historical reasons. In C++ however, they have fixed this language flaw inherited from C, so all string literals are const char[] in C++.

  1. When you use this statement

    char *str = "Stack Overflow";
    

    it means that str points to string "Stack Overflow", which is stored in code memory and user is not allowed to modify it. So for this statement, writing const before char *str doesn't have any effect because the string is already constant.

    But const plays important role when you are pointing towards the base address of array, like

    char arr[20];
    strcpy(arr,"Stack Overflow");
    const char *str = arr;
    

    Use of const means you can't modify the string in arr through the pointer str.

  2. When you are using

    char * const ptr;
    

    it means that ptr can't point to another address other than it's initial address. It is used when you do not intend to change the pointer address throughout the entire program, e.g. pointing to look up tables in embedded systems.

Related