Sunday, December 19, 2010

Qualifiers Demystified

Type qualifiers were introduced by ANSI C, it is used to control optimization done by the compiler on any data object.
There are two type of qualifiers in C:

1. const
2. volatile

const and volatile type qualifiers can be applied to any type of data objects. When using a type qualifier with an array identifier, each element of the array is qualified by the compiler, not the array type itself. A qualifier can be applied on structure/union objects and an individual structure/union member can also be qualified. When a structure object is qualified, each member of the structure is qualified using same qualifier and when a qualifier is applied on any particular structure member then only that member is qualified.

1. The 'const' qualifier:

This qualifier instructs compiler to mark any data object as read only i.e. no one can change the value of the data object during program execution. Objects qualified by the const keyword cannot be modified. This means that an object declared as const cannot serve as the operand in any operation that changes its value.

Some const qualified objects and their behavior are given below:

Constant integer:
const int Var = 10;
The value of Var can’t be modified.

Pointer to a constant integer:
const int *Ptr;
The value in the location pointed by Ptr can’t be modified i.e. *Ptr is non-modifiable but Ptr is modifiable;

Constant pointer:
int * const Cptr;   
A pointer which will always point to the same location.  Cptr is non-modifiable but *Cptr is modifiable;

A constant pointer to a constant integer:
const int *const Ccp;
Neither the pointer nor the integer can be modified. Ccp and *Ccp, both are not modifiable.

Redundant use of const:
const const int y; 
Illegal.

When a data object is declared as a global constant, it is stored in .ro (read only) segment of a program and its value can not be altered by any way, but when a constant data object is declared within a function/block’s scope, it is stored in the process stack and its value can be altered using a non constant qualified object. The scenario is described below using a code snippet.

#include<stdio.h>
const int glob=10;     /* reside inside read only data segment */
int main()
{
   const int localc=10;      /* goes onto stack and localc is marked as read only */
   int *localp1=&localc;  /* WARNING: local const accessed through a 
                                            non const  qualified object */
   int *localp2=&glob;    /* WARNING: global const accessed through a
                                            non const  qualified object */

   *localp1=0;                  /* Allowed */
   *localp2=0;                 /* Illegal: since address localp2 reside in.ro
                                          section and any alteration in .ro section results  a
                                          segmentation fault */
.
.
.
return 0;
}


Use of const qualifier with aggregate types is also explained in code snippet given below:

#include<stdio.h>

struct student{
int roll_no;
int age;
};

struct employee{
const int emp_id;
int salary;
};

int main()
{
const struct student John = {35,18};
struct employee Eric = {22,20000};
John.roll_no = 36;                     /* Illegal: John is read only */
John.age=26;                           /* Illegal: John is read only */

Eric.emp_id=23;                        /* Illegal: emp_id is read only */
Eric.salary=200000;                    /* OK */
.
.
.
.
.
return 0;
}


Attempting to modify a const object using a pointer to a non-const qualified type causes unpredictable behavior. gcc allows you to modify a constant object using non-const qualified pointer. The compiler just warns you, when any such type of casting occurs in code.

2. The 'volatile' qualifier:
The volatile qualifier alters the default behavior of the variable and does not attempt to optimize the storage referenced by it. volatile means the storage is likely to change at anytime and that is something outside the control of the user program. This means that if you reference the variable, the program should always check the physical address (i.e. a mapped input fifo), and not use it in a cached way.
This qualifier forces the compiler to allocate memory for the volatile object, and to always access the object from memory.
The use of volatile disables optimizations with respect to referencing the object. If an object is volatile qualified, it may be changed between the time it is initialized and any subsequent assignments. Therefore, it cannot be optimized.

Some volatile qualified objects and their behavior are given below:

Volatile data object:
To declare a volatile data object, include the keyword volatile before or after the data type in the variable definition. For instance both of these declarations will declare vint to be a volatile integer:

volatile int vint;
int volatile vint;

Pointer to a volatile data object:
Both of these declarations declare ptr to be a pointer to a volatile integer:

volatile int * ptr;
int volatile * ptr;

Volatile pointer to a non-volatile data object:
Following declaration declares vptr to be a  volatile pointer to non-volatile integer:

int * volatile vptr;

Volatile pointer to a volatile data object:
Following declaration declares vptrv to be a  volatile pointer to volatile integer:

int volatile * volatile vptrv;

If you apply volatile to a struct or union, the entire contents of the struct/union are volatile. If you don't want this behavior, you can apply the volatile qualifier to the individual members of the struct/union.

Use of volatile variable:
A volatile variable is used, where the value of variable changes unexpectedly.
You can see usage of volatile variables in
• Memory-mapped peripheral registers.
• Global variables modified by an interrupt service routine.
• Global variables within a multi-threaded application.
I’ll take example of memory mapped peripheral registers.

void TestFun()
{
     unsigned int  * ptr = (unsigned int *) 0x65917430;
     /* Wait for register to become non-zero. */
     while (0 == *ptr);
     printf(“Register updated\n”);
}

In this case optimization will be performed on *ptr. Assembler will generate assembly code, something like this:

mov ptr, #0x65917430    /* ptr <- 0x65917430 */
mov a, @ptr                     /* move data at address 0x65917430 to register a */                     
loop bz loop                     /* jump to loop if a is equal to zero */

Problem: It will read data from address 0x65917430 only once, if data read is zero it will go into a non-ending loop.
To handle this problem, we disable any optimization on the variable *ptr by qualifying it as volatile and the code will look like:

void TestFun()
{
     unsigned int  volatile * ptr = (unsigned int *) 0x65917430;
     /* Wait for register to become non-zero. */
     while (0 == *ptr);
     printf(“Register updated\n”);
}
Now assembler will generate assembly code
mov ptr, #0x65917430    /* ptr <- 0x65917430 */
loop mov a, @ptr            /* move data at address 0x65917430 to register a */                    
bz loop                           /* jump to loop if a is equal to zero */

Now in every execution of loop, address 0x65917430 is checked for non zero value.


______
For any suggestion or query mail me to jais.ebox@gmail.com