wiki:const

Version 8 (modified by andersk@mit.edu, 10 years ago) (diff)

Clarify which void * conversions work in C++.

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 be implicitly converted to foo *const * (as a special case of the above), but 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.

foo * may be implicitly converted to void *, hence also to const void *, but const foo * may only be converted to const void *. In C (but not C++), void * may be converted to foo *, hence also to const foo *, but const void * may only be converted to const 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

Prehistoric C compilers did not support const, so to maintain compatibility with old code, 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 *.

String functions in the standard C library accept const char * and return char *, again for compatibility; this does not cause problems because of the implicit conversion from char * to const char *. (Note that abuses such as const char *p; char *q = strstr(p, ""); are still unsafe even though they fail to be disallowed by the signature of strstr.) However, a few functions like execv that deal with pointers to strings (or arrays of strings) were forced to choose one or the other, and they take char ** or char *const * rather than const char *const *. A cast is required to use these functions with const char * strings. glibc’s getopt is a particularly special case.

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).