18.1.7: Type Qualifiers

[Type qualifiers are a fairly advanced feature which not all programs need. You may skip this section.]

Any type can be qualified by the type qualifiers const or volatile. Both of these were new with ANSI C, and there is a lot of older code which does not use them. Even in new code, you will see const fairly rarely, and volatile even less often.

In simple declarations, the type qualifier is simply another keyword in the type name, along with the basic type and the storage class. For example,

	const int i;
	const float f;
	extern volatile unsigned long int ul;
are all declarations involving type qualifiers.

A const value is one you promise not to modify. The compiler may therefore be able to make certain optimizations, such as placing a const-qualified variable in read-only memory. However, a const-qualified variable is not a true constant; that is, it does not qualify as a constant expression which C requires in certain situations, such as array dimensions, case labels (see section 18.3.1 below), and initializers for variables with static duration (globals and static locals).

A volatile value is one that might change unexpectedly. This situation generally only arises when you're directly accessing special hardware registers, usually when writing device drivers. The compiler should not assume that a volatile-qualified variable contains the last value that was written to it, or that reading it again would yield the same result that reading it last time did. The compiler should therefore avoid making any optimizations which would suppress seemingly-redundant accesses to a volatile-qualified variable. Examples of volatile locations would be a clock register (which always gave an up-to-date time value each time you read it), or a device control/status register, which caused some peripheral device to perform an action each time the register was written to.

Type qualifiers become more interesting (or at least more complicated or confusing) when they modify pointer types. The placement of the qualifier in a pointer declaration determines whether it is the pointer itself, or the location pointed to, that is qualified. The declarations

	int const *ci1;
and
	const int *ci2;
declare pointers to constant ints, which means that although the pointers can be modified (to point to different locations), the locations pointed to (that is, *ci1 and *ci2) can not be modified. The declaration
	int * const cp;
on the other hand, declares a pointer which cannot be modified (it cannot be set to point anywhere else), although the value it points to (*cp) can be modified.

Pointers to constants (such as ci1 and ci2 above) have a particularly important use: they can be used to document (and enforce) pointer parameters which a function promises not to use to modify locations in the caller.

Normally, C uses pass-by-value. A function receives copies of its arguments, which means that it cannot modify any variables in the caller (since copies of those variables were passed). If a function receives a pointer, however (including the pointer that results when the caller seems to ``pass'' an array), it can use that pointer (more precisely, it can use its copy of the pointer) to modify locations in the caller. Sometimes, this is just what is desired: when the caller ``passes'' an array which it wishes the function to fill in, or when the function wants to return one or more values via pointers rather than as the conventional return value, the function's modification of locations in the caller is deliberate and understood by the caller. However, when a function receives a pointer argument for some other reason, under circumstances in which the caller might not want the function to use the pointer to modify anything in the caller, the caller might appreciate a guarantee that the pointer (within the function) won't be used to modify anything. To make that guarantee, the function can declare the pointer as pointer-to-const.

For example, our old friend printf never scribbles on the string it's given as its format argument; it merely uses it to decide what to print. Therefore, the prototype for printf is

	int printf(const char *fmt, ...)
where the ... represents printf's optional arguments. If a caller writes something like
	char mystring[] = "Hello, world!\n";
	printf(mystring);
it knows, from printf's prototype, that printf won't be scribbling on mystring. Furthermore, with that prototype for printf in scope, the actual author of the printf code couldn't accidentally write a (buggy) version which inadvertently modified the format argument--since it's declared as const char *, the compiler will complain if any attempt is made to write to the location(s) it points to.

const and volatile can also be used in combination. Theoretically, it's possible to have a single variable which is both:

	const volatile int x;
Also, both a pointer and what it points to can be qualified:
	const char * const cpc;

Finally, as in several other situations, C tends to assume type int, so if you want to save a bit of typing, you can write

	const i;
instead of
	const int i;
etc.


Read sequentially: prev next up top

This page by Steve Summit // Copyright 1996-1999 // mail feedback