It took me a long time to understand that the
const qualifier in C is overloaded. There was no
const when I first learned C. It was introduced in the C90 standard. It was copied from C++, although the meaning of
const is subtly different in the two languages.
The first meaning of
const in C is to use it to qualify a variable definition. If you do that, it means that the variable can not be changed; it is immutable. It can have an initializer but will never have another value. A global or static
const variable may be placed in read-only memory.
The second meaning is to use it to qualify a pointer. In this usage it means that the program may not change the value being pointed to by using the pointer. However, it does not mean that that value is immutable, as it may be changed by a different pointer, or by a function call, or by casting away the
The first meaning of
const has a real effect on the program. A variable declared
const may be compiled differently from a variable which is not declared
const. The second meaning of
const, on the other hand, is, in effect, compiler-enforced documentation. The compiler will issue an error if an attempt is made to change a value using a
const qualified pointer, but declaring such a pointer will not change the generated code.
const qualified pointers are just documentation, in practice they are used far more often than
const variables. When the C90 standard came out and C compilers started supporting
const, programmers spoke of
const-poisoning: the feeling that once you use the
const qualifier anywhere, it spread throughout your program as it had to be tracked through all assignments and function calls to avoid compiler warnings. Adding
const-poisoning to a program does not make it any more correct or reliable. It’s just documentation, albeit documentation that the compiler enforces.
One can imagine a range of different types of documentation which it might be nice to have the compiler enforce. Perhaps it would be useful to know the alignment of a generic pointer, or to know that this
char* pointer points to a null terminated string while that one points to a block of arbitrary bytes. A program can use a
typedef to indicate the intended meaning of some value, but a pointer to one
typedef can be assigned to a pointer to a different
typedef with no warning if the underlying types happen to be the same. Why do we get compiler warnings for
const pointers but not for
typedef pointers? It’s because
const was added to the type system, whereas
typedef, despite its name, is merely an aliasing mechanism, and is not part of the type system at all.
It did not have to be this way. It would have been possible to make
const a storage specifier, like
static, rather than a type qualifier. It would have simply meant that the variable was immutable. The address of a
const int would have type
int * rather than
const int *.
The C90 standard introduced one other type qualifier:
volatile (in fact, the standard introduced the whole idea of type qualifiers). Unlike
const, the standard did not define the meaning of a
volatile qualified pointer. The address of a
volatile variable has a
volatile qualified pointer type, but the standard never said what the compiler should do with such a type. It only says that any access to a
volatile variable must be through an lvalue with the
The C99 standard adds a third type qualifier,
restrict, which is another can of worms.
In retrospect I think that adding the
const qualifier to the type system was a mistake. It lets the compiler enforce a specific type of documentation but doesn’t let the program define the types of documentation that it cares about. The documentation is not reliable as programs can cast away the qualifier, and indeed the standard requires basic functions like
strchr to do exactly that.
If you want the compiler to be able to check type attributes, then give the language a way to define the attributes that matter to a particular program. If you want to compiler to be able to put a variable in a specific type of storage, such as a read-only data section, then use a storage specifier. There is no need to mix the two ideas.