wiki:const

Version 5 (modified by andersk@mit.edu, 14 years ago) (diff)

Try to clarify the table by distinguishing between “changing” a char and “repointing” a pointer.

Notes on const correctness

Syntax review

const int x;
int const x;
The value of the int cannot be changed.
const int *p;
int const *p;
The value of the int cannot be changed through this pointer (*p = n is disallowed).
int *const p;The pointer cannot be repointed (p = q is disallowed).
const int *const p;The pointer cannot be repointed, and the value of the int cannot be changed through this pointer.
const int a[3];The values of the ints in the array cannot be changed.
const char **pp;The char pointed to by *pp cannot be changed through it.
char *const *pp;The char * pointed to by pp cannot be repointed through it.
char **const pp;pp cannot be repointed.
const char *const *pp;The const char * cannot be repointed through pp, and the char cannot be changed through *pp.
const char *const *const pp;All of the above.

foo * may be implicitly converted to const foo *, but not the other way around.

foo ** may not be converted to const foo **. (Otherwise, you could write through const foo *p as follows: foo *q; const foo **pq = &q; *pq = p; *q = …;)

However, it is safe to convert foo ** to const foo *const *. C++ lets you do this implicitly (conv.qual 4 in the standard); for no particular reason, C does not.

In C, void * may be implicitly converted to and from foo *, and hence also to const foo *. const void * may be implicitly converted to and from const foo *, and hence also from foo *.

How const relates to malloc and free

Note the prototypes of malloc and free:

void *malloc(size_t size);
void free(void *ptr);

This implies that a foo cannot be freed through a const foo *. Therefore, the code that “owns” (i.e., is responsible for allocating and freeing) the foo must hold on to the (non-const) foo *. When possible, it should pass only a const foo * to any other code that does not need to modify or free the foo.

Do not try to subvert this rule by adding casts! Code that needs to free a foo should not have been given a const foo *. A cast only hides this potential bug. gcc -Wcast-qual will warn you about casts that throw away const qualifiers.

These rules can help you locally find memory leaks and double-free bugs. For example, if a foo is malloc’d and only stored into a const foo *, you know that it has been leaked. If you ignore a compiler warning that const foo * is converted into foo *, you may be freeing a foo that was not supposed to be freed.

const and strings

For historical reasons, literal strings have type char[] by default. However, literal strings are still put into a read-only section of the program image, and it is invalid to modify or free one. Therefore literal strings should always be stored in a const char *, never in a char *.

If you compile with gcc -Wwrite-strings, literal strings will be typed const char[] like they should be, to prevent you from making the mistake of storing one in a char *.

Using const effectively

If C had been designed with perfect foresight, const would have been the default on all types, and a qualifier would have been required to allow a value to be modified or freed. But, we do not live in a perfect world, and writing const absolutely everywhere would be a waste of typing and make your code harder to read.

It is usually not useful to qualify something that is not the target of a pointer type, such as a simple local variable (const int n;), or a function parameter or return type (const int fib(const int n);). That would add no safety because any modifications to the parameter of int fib(int n) are not visible to the caller, and the return value cannot be modified in place anyway.

However, you should always make sure that the target of a pointer type is correctly const-qualified. Think carefully about whether to use const foo **p and/or foo *const *p (but don’t worry about foo **const p).