The keyword const was one of the C++ features included in ISO C. When the analogous keyword, volatile, was invented by the ISO C Committee, the type qualifier category was created.
const and volatile are part of an identifier’s type, not its storage class. However, they are often removed from the topmost part of the type when an object’s value is fetched in the evaluation of an expression, exactly at the point when an lvalue becomes an rvalue. These terms arise from the prototypical assignment left-hand-side=right-hand-side; in which the left side must still refer directly to an object (an lvalue) and the right side need only be a value (an rvalue). Thus, only expressions that are lvalues can be qualified by const or volatile or both.
The type qualifiers may modify type names and derived types. Derived types are those parts of declarations in C that can be applied repeatedly to build more and more complex types: pointers, arrays, functions, structures, and unions. Except for functions, one or both type qualifiers can be used to change the behavior of a derived type.
The following example declares and initializes an object with type const int whose value is not changed by a correct program.
const int five = 5;
The order of the keywords is not significant to C. For example, the following declarations are identical to the first example in its effect:
int const five = 5;
const five = 5;
The following declaration declares an object with type pointer to const int, which initially points to the previously declared object.
const int *pci = &five;
The pointer itself does not have a qualified type, but rather it points to a qualified type. It can be changed to point to essentially any int during program execution. pci cannot be used to modify the object to which it points unless a cast is used, as in the following example:
*(int *)pci = 17;
If pci actually points to a const object, the behavior of this code is undefined.
The following declaration indicates that somewhere in the program is a definition of a global object with type const pointer to int.
extern int *const cpi;
In this case, cpi’s value will not be changed by a correct program, but it can be used to modify the object to which it points. Notice that const comes after the * in the declaration. The following pair of declarations produces the same effect:
typedef int *INT_PTR; extern const INT_PTR cpi;
const int *const cpci;
In hindsight, readonly would have been a better choice for a keyword than const. If one reads const in this manner, declarations such as the following example, are easily understood to mean that the second parameter is only used to read character values, while the first parameter overwrites the characters to which it points:
char *strcpy(char *, const char *);
Furthermore, despite the fact that in the example the type of cpi is a pointer to a const int, you can still change the value of the object to which it points through some other means, unless it actually points to an object declared with const int type.
The two main uses for const are to declare large compile-time initialized tables of information as unchanging, and to specify that pointer parameters do not modify the objects to which they point.
The first use potentially allows portions of the data for a program to be shared by other concurrent invocations of the same program. It might cause attempts to modify this invariant data to be detected immediately by means of some sort of memory protection fault, because the data resides in a read-only portion of memory.
The second use of const helps locate potential errors before generating a memory fault. For example, functions that temporarily place a null character into the middle of a string are detected at compile time, if passed a pointer to a string that cannot be so modified.
So far, the examples have shown const to be conceptually simple. But what does volatile really mean? For the compiler, it means don't take any code generation shortcuts when accessing such an object. On the other hand, ISO C makes it the programmer's responsibility to declare volatile every object that has the appropriate special properties.
The usual four examples of volatile objects are:
An object that is a memory-mapped I/O port
An object that is shared between multiple concurrent processes
An object that is modified by an asynchronous signal handler
An automatic storage duration object declared in a function that calls setjmp, and whose value is changed between the call to setjmp and a corresponding call to longjmp
The first three examples are all instances of an object with a particular behavior: its value can be modified at any point during the execution of the program. Thus, the following seemingly infinite loop is valid as long as flag has a volatile qualified type.
flag = 1; while (flag);
Presumably, some asynchronous event sets flag to zero in the future. Otherwise, because the value of flag is unchanged within the body of the loop, the compilation system is free to change the above loop into a truly infinite loop that completely ignores the value of flag.
The fourth example, involving variables local to functions that call setjmp, is more involved. The details about the behavior of setjmp and longjmp indicates that the values for objects matching the fourth case are unpredictable. For the most desirable behavior, longjmp must examine every stack frame between the function calling setjmp and the function calling longjmp for saved register values. The possibility of asynchronously created stack frames makes this job even harder.
When an automatic object is declared with a volatile qualified type, the compiler must generate code that exactly matches what the programmer wrote. Therefore, the most recent value for such an automatic object is always in memory and not just in a register, and is guaranteed to be up-to-date when longjmp is called .