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 const
qualifier.
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.
Although 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 volatile
qualifier.
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.
Leave a Reply
You must be logged in to post a comment.