8.4. Const and volatileThese are new in Standard C, although the idea of Since char long float volatile short signed double void int unsigned const In that list, char, signed char, unsigned char int, signed int, unsigned int short int, signed short int, unsigned short int long int, signed long int, unsigned long int float double long double A few points should be noted. All declarations to do with an
The keywords
volatile i;
volatile int j;
const long q;
const volatile unsigned long int rt_clk;
struct{
const long int li;
signed char sc;
}volatile vs;
Don't be put off; some of them are deliberately complicated: what they mean will be explained later. Remember that they could also be further complicated by introducing storage class specifications as well! In fact, the truly spectacular extern const volatile unsigned long int rt_clk; is a strong possibility in some real-time operating system kernels. 8.4.1. ConstLet's look at what is meant when Taking the address of a data object of a type which isn't
#include <stdio.h>
#include <stdlib.h>
main(){
int i;
const int ci = 123;
/* declare a pointer to a const.. */
const int *cpi;
/* ordinary pointer to a non-const */
int *ncpi;
cpi = &ci;
ncpi = &i;
/*
* this is allowed
*/
cpi = ncpi;
/*
* this needs a cast
* because it is usually a big mistake,
* see what it permits below.
*/
ncpi = (int *)cpi;
/*
* now to get undefined behaviour...
* modify a const through a pointer
*/
*ncpi = 0;
exit(EXIT_SUCCESS);
}Example 8.3As the example shows, it is possible to take the address of a constant object, generate a pointer to a non-constant, then use the new pointer. This is an error in your program and results in undefined behaviour. The main intention of introducing const objects was to allow them to
be put into read-only store, and to permit compilers to do extra
consistency checking in a program. Unless you defeat the intent by doing
naughty things with pointers, a compiler is able to check that
An interesting extra feature pops up now. What does this mean? char c; char *const cp = &c; It's simple really; const char *cp; which means that now cp is an ordinary, modifiable pointer, but the thing that it points to must not be modified. So, depending on what you choose to do, both the pointer and the thing it points to may be modifiable or not; just choose the appropriate declaration. 8.4.2. VolatileAfter const, we treat Let's imagine that the device has two registers, each 16 bits long, at ascending memory addresses; the first one is the control and status register (csr) and the second is a data port. The traditional way of accessing such a device is like this:
/* Standard C example but without const or volatile */
/*
* Declare the device registers
* Whether to use int or short
* is implementation dependent
*/
struct devregs{
unsigned short csr; /* control & status */
unsigned short data; /* data port */
};
/* bit patterns in the csr */
#define ERROR 0x1
#define READY 0x2
#define RESET 0x4
/* absolute address of the device */
#define DEVADDR ((struct devregs *)0xffff0004)
/* number of such devices in system */
#define NDEVS 4
/*
* Busy-wait function to read a byte from device n.
* check range of device number.
* Wait until READY or ERROR
* if no error, read byte, return it
* otherwise reset error, return 0xffff
*/
unsigned int read_dev(unsigned devno){
struct devregs *dvp = DEVADDR + devno;
if(devno >= NDEVS)
return(0xffff);
while((dvp->csr & (READY | ERROR)) == 0)
; /* NULL - wait till done */
if(dvp->csr & ERROR){
dvp->csr = RESET;
return(0xffff);
}
return((dvp->data) & 0xff);
}Example 8.4The technique of using a structure declaration to describe the device register layout and names is very common practice. Notice that there aren't actually any objects of that type defined, so the declaration simply indicates the structure without using up any store. To access the device registers, an appropriately cast constant is used as if it were pointing to such a structure, but of course it points to memory addresses instead. However, a major problem with previous C compilers would be in the
while loop which tests the status register and waits for the
Because of this problem, most C compilers have been unable to make
that sort of optimization in the past. To remove the problem (and other
similar ones to do with when to write to where a pointer points), the
keyword Here is how you would rewrite the example, making use of
/*
* Declare the device registers
* Whether to use int or short
* is implementation dependent
*/
struct devregs{
unsigned short volatile csr;
unsigned short const volatile data;
};
/* bit patterns in the csr */
#define ERROR 0x1
#define READY 0x2
#define RESET 0x4
/* absolute address of the device */
#define DEVADDR ((struct devregs *)0xffff0004)
/* number of such devices in system */
#define NDEVS 4
/*
* Busy-wait function to read a byte from device n.
* check range of device number.
* Wait until READY or ERROR
* if no error, read byte, return it
* otherwise reset error, return 0xffff
*/
unsigned int read_dev(unsigned devno){
struct devregs * const dvp = DEVADDR + devno;
if(devno >= NDEVS)
return(0xffff);
while((dvp->csr & (READY | ERROR)) == 0)
; /* NULL - wait till done */
if(dvp->csr & ERROR){
dvp->csr = RESET;
return(0xffff);
}
return((dvp->data) & 0xff);
}Example 8.5The rules about mixing If an array, union or structure is declared with That means that an alternative rewrite of the last example would be
possible. Instead of declaring the device registers to be
struct devregs{
unsigned short csr; /* control & status */
unsigned short data; /* data port */
};
volatile struct devregs *const dvp=DEVADDR+devno;
Since So, for any object likely to be subject to modification either by hardware or asynchronous interrupt service routines, the volatile type qualifier is important. Now, just when you thought that you understood all that, here comes the final twist. A declaration like this:
volatile struct devregs{
/* stuff */
}v_decl;
declares the type struct devregs nv_decl; declares
struct devregs{
/* stuff */
}volatile v_decl;
If you do want to get a shorthand way of attaching a qualifier to
another type, you can use
struct x{
int a;
};
typedef const struct x csx;
csx const_sx;
struct x non_const_sx = {1};
const_sx = non_const_sx; /* error - attempt to modify a const */
8.4.2.1. Indivisible OperationsThose of you who are familiar with techniques that involve hardware
interrupts and other ‘real time’ aspects of programming will
recognise the need for Be careful not to assume that any operations written in C are uninterruptable. For example, extern const volatile unsigned long realtimeclock; could be a counter which is updated by a clock interrupt routine. It
is essential to make it unsigned long int time_of_day; time_of_day = real_time_clock; there may be a problem. What if, to copy one This whole class of problem is what is known as a critical region, and is well understood by those who regularly work in asynchronous environments. It should be understood that Standard C takes no special precautions to avoid these problems, and that the usual techniques should be employed. The header |
The C BookThis book is published as a matter of historical interest. Please read the copyright and disclaimer information. GBdirect Ltd provides up-to-date training and consultancy in C, Embedded C, C++ and a wide range of other subjects based on open standards if you happen to be interested. |
|
West Yorkshire Office
GBdirect Ltd
Training: 0800 651 0338 Please call between 0900 and 1700 (UK time) on Monday to Friday South East Regional Office
GBdirect Ltd
Training: 0800 651 0338 Please call between 0900 and 1700 (UK time) on Monday to Friday Please note: |