Search

Sponsored Links

Meta

Categories

Archives

Recent Posts

RSS Feeds

28
May

Constant and Volatile

Related Blog Items

Any type can be qualified by the type qualifiers const or volatile.In simple declarations, the type qualifier is simply another keyword in the type name, along with the basic type and the storage class.

Syntax for the const and volatile keywords

int * volatile x;    /* x is a volatile pointer to an int */
int * const y = &z;  /* y is a const pointer to the int variable z */

For a pointer to a volatile or const data object, the type specifier, qualifier, and storage class specifier can be in any order. For example:

volatile int *x;         /* x is a pointer to a volatile int
    or                                                      */
int volatile *x;         /* x is a pointer to a volatile int*/
 
const int *y;            /* y is a pointer to a const int
    or                                                      */
int const *y;            /* y is a pointer to a const int   */

In the following example, the pointer to y is a constant. You can change the value that y points to, but you cannot change the value of y:

int * const y

In the following example, the value that y points to is a constant integer and cannot be changed. However, you can change the value of y:

const int * y

For other types of volatile and const variables, the position of the keyword within the definition (or declaration) is less important. For example:

volatile struct omega 
{
  int limit;
  char code;
} group;

provides the same storage as:

struct omega 
{
  int limit;
  char code;
} volatile group;

In both examples, only the structure variable group receives the volatile qualifier. Similarly, if you specified the const keyword instead of volatile, only the structure variable group receives the const qualifier. The const and volatile qualifiers when applied to a structure, union, or class also apply to the members of the structure, union, or class.

Although enumeration, class, structure, and union variables can receive the volatile or const qualifier.enumeration, class, structure, and union tags do not carry the volatile or const qualifier. For example, the blue structure variable does not carry the volatile qualifier:

volatile struct whale 
{
  int weight;
  char name[8];
} green;
struct whale blue;

The keywords volatile and const cannot separate the keywords enum, class, struct, and union from their tags. i.e

struct volatile whale //compiler gives error
{
  //.....
}blue; 
 
Struct const whale   //compiler gives error
{  
  //......
}blue;

An item can be both const and volatile. In this case the item cannot be legitimately modified by its own program but can be modified by some asynchronous process.

extern const volatile int real_time_clock;

may be modifiable by hardware(because of volatile), but cannot be assigned to, incremented, or decremented(because of const).

Constant as a promise:

Const is a promise that you make to the compiler not to modify. The compiler may therefore be able to make certain optimizations, such as placing a const-qualified variable in read-only memory.

C and C++ permit certain conversions between similar pointer types that differ only in their use of const. For example, given:

T *p;
...
void f(T const *qc);

a program can call f(p). In passing p to f, the program converts p’s value from “pointer to T” into “pointer to const T.” This is a valid conversion.
In contrast, given:

90f5db41b3feccf4d81208dda91330b3010

calling g(pc) produces a compile-time error. The compiler rejects the attempt to convert pc’s value from “pointer to const T” into “pointer to T.”

It’s easy to see the rationale for these conversion rules once you start to think of const as a promise. you need not make any promises, but once you make one, you must keep it. Here’s how it applies.
The presence of const in a declaration such as:

int const *p;

is effectively a promise that the program will not use a value obtained directly or indirectly from p to modify any int objects. The compiler enforces the promise by rejecting any operation that attempts to modify an object accessed as *p, as in:

*p += 3;	// error
++(*p);     // error

Neither expression will compile.

The declaration for p is not a promise that p will point only to constant objects. In fact, p can point to either const or nonconst objects. It’s just that p promises to treat any objects it might point to as if they all were constant.

For example, the Standard C function strlen is declared as:

size_t strlen(char const *s);

In effect, strlen promises not to alter the characters of any array passed to it. Calling strlen(buf), where buf is some character array, doesn’t promise that the characters in buf won’t change. It only promises that strlen won’t be the one to change them.

Once you understand const as a promise, it’s easy to see why, given:

T const *pc;
...	
void g(T *q);

you can’t call g(pc). The declaration of pc promises that the program won’t use the value in pc to modify any T objects. However, the declaration for g’s parameter q makes no such promise. This doesn’t mean that function g will necessarily change the value that q points to, but it does mean that g is leaving that option open. Therefore, the compiler can’t entrust q with a copy of pc, because g might then use q to violate the promise in pc’s declaration.
On the other hand, given:

 
T *p;
...
void f(T const *qc);

then calling f(p) poses no problem. In this case, p’s declaration makes no promises, so calling f(p) can’t violate any promise. However, the declaration for f’s parameter qc makes a promise that f won’t use the pointer value in qc to modify any T objects. The compiler will enforce this promise when it compiles the body of f. This means, for example, that f must not call g(qc), because g’s parameter q doesn’t keep the promise.

Volatile as a promise:

A const object is one whose value the program can’t change, a volatile object is one whose value might change spontaneously and unexpectedly. That is, when you declare an object to be volatile, you’re telling the compiler that the object might change state even though no statements in the program appear to change it.

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.
Most processors can perform operations faster when the operands reside in CPU registers rather than in ordinary memory. Compilers can optimize operations on nonvolatile objects by reading an object’s value into a CPU register, working with that register for a while, and eventually writing the value in the register back to the object. Compilers can’t do this sort of optimization with volatile objects. Every time the source program says to read from or write to a volatile object, the compiled code must do so.
Even though volatile has a different meaning from const, you can still regard volatile as a promise. The presence of volatile in a declaration such as:

T volatile *pv;

is effectively a promise that the compiler won’t try to optimize the accesses to or from any T object referenced via a value obtained directly or indirectly from pv. By analogy with const, this is not a promise that pv will point only to volatile T objects; pv can point to either volatile or nonvolatile T objects. It’s just that pv promises to treat any T objects it might point to as if they all were volatile.
Once you understand volatile as a promise, it’s no surprise that the conversion rules regarding “pointer to volatile” are identical to those regarding “pointer to const.” For example, given:

T volatile *pv;
...
void g(T *q);

you can’t call g(pv). The declaration of pv promises that the program will treat whatever pv points to as if it were volatile. However, the declaration for g’s parameter q makes no such promise. Therefore, the compiler can’t entrust q with a copy of pv, because q might violate the promise.
On the other hand, given:

T *p;
...
void h(T volatile *qv);

then calling f(p) compiles without complaint. In this case, p’s declaration makes no promises, so calling h(p) can’t violate anything. However, the declaration for h’s parameter qv makes a promise that the compiler won’t optimize the accesses to any T objects accessed via pv. The code generated for h’s function body might be less than optimal, but it won’t mistreat any T objects.

Who?s making the promise?

If const and volatile are indeed promises, who’s really making those promises?
Const is a promise that you make to the compiler. When you declare p as a “pointer to const T,” you’re promising the compiler that you (or your program, if you prefer) will treat *p as a nonmodifiable object. Moreover, you’re enlisting the compiler’s aid in helping you keep that promise, something it’s more than happy to do.

On the other hand, volatile is a promise that you elicit from the compiler. When you declare p as a “pointer to volatile T,” you’re asking the compiler to promise you that it won’t optimize any accesses to *p. In response, the compiler demands that you help it keep that promise by obeying certain conversion rules.

Popularity: 11%

You need to log on to convert this article into PDF


Related Blog Items

No Comments

No comments yet.

Leave a comment

*
To prove you're a person (not a spam script), type the security word shown in the picture.
Anti-spam image