const 和volatile的用法

一:告诉compiler不能做任何优化
比如要往某一地址送两指令:
int *ip =...; //设备地址
*ip = 1; //第一个指令
*ip = 2; //第二个指令
以上程序compiler可能做优化而成:
int *ip = ...;
*ip = 2;
结果第一个指令丢失。如果用volatile, compiler就不允许做任何的优化,从而保证程序的原意:
volatile int *ip = ...;
*ip = 1;
*ip = 2;
即使你要compiler做优化,它也不会把两次付值语句间化为一。它只能做其它的优化。这对device driver程序员很有用。
二:表示用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,而不能把他放在cache或寄存器中重复使用。
如 volatile char a;
a=0;
while(!a){
//do some things;
}
doother();
如果没有 volatile doother()不会被执行

还找到一篇比较详细的比较CONST和Volatile的文章

8.4. Const and volatile

These are new in Standard C, although the idea of const has been borrowed from C++. Let us get one thing straight: the concepts of const and volatile are completely independent. A common misconception is to imagine that somehow const is the opposite of volatile and vice versa. They are unrelated and you should remember the fact.

Since const declarations are the simpler, we'll look at them first, but only after we have seen where both of these type qualifiers may be used. The complete list of relevant keywords is

char      long      float     volatile
short     signed    double    void
int       unsigned  const

In that list, const and volatile are type qualifiers, the rest are type specifiers. Various combinations of type specifiers are permitted:

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 int will be signed anyway, so signed is redundant in that context. If any other type specifier or qualifier is present, then the int part may be dropped, as that is the default.

The keywords const and volatile can be applied to any declaration, including those of structures, unions, enumerated types or typedef names. Applying them to a declaration is called qualifying the declaration—that's why const and volatile are called type qualifiers, rather than type specifiers. Here are a few representative examples:

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. Const

Let's look at what is meant when const is used. It's really quite simple: const means that something is not modifiable, so a data object that is declared with const as a part of its type specification must not be assigned to in any way during the run of a program. It is very likely that the definition of the object will contain an initializer (otherwise, since you can't assign to it, how would it ever get a value?), but this is not always the case. For example, if you were accessing a hardware port at a fixed memory address and promised only to read from it, then it would be declared to be const but not initialized.

Taking the address of a data object of a type which isn't const and putting it into a pointer to the const-qualified version of the same type is both safe and explicitly permitted; you will be able to use the pointer to inspect the object, but not modify it. Putting the address of a const type into a pointer to the unqualified type is much more dangerous and consequently prohibited (although you can get around this by using a cast). Here is an example:

#include 
#include 
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.3

As 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 const objects are not modified explicitly by the user.

An interesting extra feature pops up now. What does this mean?

char c;
char *const cp = &c;

It's simple really; cp is a pointer to a char, which is exactly what it would be if the const weren't there. The const means that cp is not to be modified, although whatever it points to can be—the pointer is constant, not the thing that it points to. The other way round is

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. Volatile

After const, we treat volatile. The reason for having this type qualifier is mainly to do with the problems that are encountered in real-time or embedded systems programming using C. Imagine that you are writing code that controls a hardware device by placing appropriate values in hardware registers at known absolute addresses.

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.4

The 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 ERROR or READY bit to come on. Any self-respecting optimizing compiler would notice that the loop tests the same memory address over and over again. It would almost certainly arrange to reference memory once only, and copy the value into a hardware register, thus speeding up the loop. This is, of course, exactly what we don't want; this is one of the few places where we must look at the place where the pointer points, every time around the loop.

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 volatile was introduced. It tells the compiler that the object is subject to sudden change for reasons which cannot be predicted from a study of the program itself, and forces every reference to such an object to be a genuine reference.

Here is how you would rewrite the example, making use of const and volatile to get what you want.

/*
* 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.5

The rules about mixing volatile and regular types resemble those for const. A pointer to a volatile object can be assigned the address of a regular object with safety, but it is dangerous (and needs a cast) to take the address of a volatile object and put it into a pointer to a regular object. Using such a derived pointer results in undefined behaviour.

If an array, union or structure is declared with const or volatile attributes, then all of the members take on that attribute too. This makes sense when you think about it—how could a member of a const structure be modifiable?

That means that an alternative rewrite of the last example would be possible. Instead of declaring the device registers to be volatile in the structure, the pointer could have been declared to point to a volatile structure instead, like this:

struct devregs{
unsigned short  csr;    /* control & status */
unsigned short  data;   /* data port */
};
volatile struct devregs *const dvp=DEVADDR+devno;

Since dvp points to a volatile object, it not permitted to optimize references through the pointer. Our feeling is that, although this would work, it is bad style. The volatile declaration belongs in the structure: it is the device registers which are volatile and that is where the information should be kept; it reinforces the fact for a human reader.

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 and also a volatile-qualified object of that type, called v_decl. A later declaration like this

struct devregs nv_decl;

declares nv_decl which is not qualified with volatile! The qualification is not part of the type of struct devregs but applies only to the declaration of v_decl. Look at it this way round, which perhaps makes the situation more clear (the two declarations are the same in their effect):

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 typedef to do it:

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 Operations

Those of you who are familiar with techniques that involve hardware interrupts and other ‘real time’ aspects of programming will recognise the need for volatile types. Related to this area is the need to ensure that accesses to data objects are ‘atomic’, or uninterruptable. To discuss this is any depth would take us beyond the scope of this book, but we can at least outline some of the issues.

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 volatile because of the asynchronous updates to it, and it is marked const because it should not be changed by anything other than the interrupt routine. If the program accesses it like this:

unsigned long int time_of_day;
time_of_day = real_time_clock;

there may be a problem. What if, to copy one long into another, it takes several machine instructions to copy the two words making up real_time_clock and time_of_day? It is possible that an interrupt will occur in the middle of the assignment and that in the worst case, when the low-order word of real_time_clock is 0xffff and the high-order word is 0x0000, then the low-order word of time_of_day will receive 0xffff. The interrupt arrives and increments the low-order word of real_time_clock to 0x0 and then the high-order word to 0x1, then returns. The rest of the assignment then completes, with time_of_day ending up containing 0x0001ffff and real_time_clock containing the correct value, 0x00010000.

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 ‘signal.h’ declares a type called sig_atomic_t which is guaranteed to be modifiable safely in the presence of asynchronous events. This means only that it can be modified by assigning a value to it; incrementing or decrementing it, or anything else which produces a new value depending on its previous value, is not safe.



下面是一篇专门讲volatile的

Use of volatile

The compilation system tries to reduce code size and execution time on all machines, by optimizing code. It is programmer responsibility to inform compilation system that certain code (or variable) should not be optimized. Many programmers do not understand when to use volatile to inform compilation system that certain code should not be optimized or what it does. Although (no doubt) their program work, they do not exploit the full power of the language.

A variable should be declared volatile whenever its value can be changed by something beyond the control of the program in which it appears, such as a concurrently executing thread. Volatile, can appear only once in a declaration with any type specifier; however, they cannot appear after the first comma in a multiple item declaration. For example, the following declarations are legal

/* Let T denotes some data type */
typedef volatile T i; 
volatile T i;
T volatile   i ; 
And the following declaration is illegal
T i, volatile vi ;
Volatile qualifiers can be used to change the behavior of a type. For example,
volatile int p = 3;
declares and initializes an object with type volatile int whose value will be always read from memory.

Syntax

Keyword volatile can be placed before or after the data type in the variable definition. For example following declaration are identical:
Volatile T a =3;
T volatile a=3; 
The declaration declares and initializes an objet with type volatile T whose value will be always read from memory

Pointer declaration

Volatile T * ptr; 
T volatile * ptr; 
Ptr is a a pointer to a volatile T:
volatile pointer to a volatile variable  
int volatile * volatile ptr; 
volatile can be applied to derived types such as an array type ,in that case, the element is qualified, not the array type. When applied to struct types entire contents of the struct become volatile. You can apply the volatile qualifier to the individual members of the struct. Type qualifiers are relevant only when accessing identifiers as l-values in expressions. volatile does not affects the range of values or arithmetic properties of the object.. To declare the item pointed to by the pointer as volatile, use a declaration of the form:
volatile T *vptr;


To declare the value of the pointer - that is, the actual address stored in the pointer - as volatile, use a declaration of the form:
T* volatile ptrv;

Use of volatile

- An object that is a memory-mapped I/O port
- An object variable that is shared between multiple concurrent processes
- An object that is modified by an interrupt service routine
- An automatic object declared in a function that calls setjmp and whose value is-changed between the call to setjmp and a corresponding call to longjmp

# An object that is a memory-mapped I/O port

an 8 bit , memory -mapped I/O port at physical address 0X15 can be declared as
char const ptr=(char*)0X15 ;
*ptr access the port consider following code fragment to set the third bit of the output port at periodic intervals
*ptr = 0;
while(*ptr){
*ptr = 4 ;
*ptr = 0 ;
}
the above code may be optimize as
*ptr = 0
while(0) {
}
*ptr is assigned 0 before value 4 is used ( and value of *ptr never changes ( same constant is always assigned to it)
volatile keyword is used to suppress these optimization the compiler assumes that the value can change any time , even if no explicit code modify it to suppress all optimization declare ptr as
 volatile char * const ptr = (volatile char*)0x16
this declaration say the object at which ptr points can change without notice , but that ptr itself is a constant whose value never change

# An object that is shared between multiple concurrent processes

if two threads/tasks concurrently assign distinct values to the same shared non-volatile variable, a subsequent use of that variable may obtain a value that is not equal to either of the assigned values, but some implementation-dependent mixture of the two values. so a global variable references by multiple thread programmers must declare shared variable as volatile.
#include
#include
volatile int num ;
void* foo()
{
while(1) {
++num ;
sleep(1000);
}
}
main()
{
int p ;
void *targ =NULL ;
thread_t id ;
num = 0;
p = thr_create((void*)NULL , 0,foo,targ,0,&id);
if(!p)  printf(" can not create thread ");
while(1) 
{
printf("%d" , num ) ;
}
}
the compiler may use a register to store the num variable in the main thread , so the change to the value by the second thread are ignored . The volatile modifier is a away of telling the compiler that no optimization applied to the variable its value be not placed in register and value may change outside influence during evaluation

# An automatic object declared in a function that calls setjmp and whose value is-changed between the call to setjmp and a corresponding call to longjmp

Variables local to functions that call setjmp is most often used. When an automatic object is declared in a function that calls setjmp, the compilation system knows that it has to produce code that exactly matches what the programmer wrote. Therefore, the most recent value for such an automatic object will always be in memory (not just in a register) and as such will be guaranteed to be up-to-date when longjmp is called. Without volatile variable is undefined by the standard whether the result one see differs with or without optimization simply reflects that fact Consider following code
#include
static jmp_buf  buf ;
main( )
{
volatile  int b;
b =3 ;
if(setjmp(buf)!=0)  {
printf("%d ", b) ;  
exit(0);
}
b=5;
longjmp(buf , 1) ;
}
volatile variable isn't affected by the optimization. So value of b is after the longjump is the last value variable assigned. Without volatile b may or may not be restored to its last value when the longjmp occurs. For clear understanding assembly listing of above program by cc compiler has been given below.
/*Listing1: Assembly code fragment of above program
Produced by cc compiler  when volatile is used*/
.file	"vol.c"
main:
pushl	%ebp
movl	%esp, %ebp
subl	$20, %esp
movl	$3, -4(%ebp)
pushl	$buf
call	_setjmp
addl	$16, %esp
testl	%eax, %eax
je	.L18
subl	$8, %esp
movl	-4(%ebp), %eax
pushl	%eax
pushl	$.LC0
call	printf
movl	$0, (%esp)
call	exit
.p2align 2
.L18:
movl	$5, -4(%ebp)
subl	$8, %esp
pushl	$1
pushl	$buf
call	longjmp

 /*Listing 2:Assemply code fragment of  above program
produced by cc compile witout volatile keword */
.file	"wvol.c"
main:
pushl	%ebp
movl	%esp, %ebp
subl	$20, %esp
pushl	$buf
call	_setjmp
addl	$16, %esp
testl	%eax, %eax
je	.L18
subl	$8, %esp
pushl	$3
pushl	$.LC0
call	printf
movl	$0, (%esp)
call	exit
.p2align 2
.L18:
subl	$8, %esp
pushl	$1
pushl	$buf
call	longjmp

/listing 3: difference between listing 1 and listing 3 ***/
< .file	"vol.c"
---
> .file	"wvol.c"
< 	movl	$3, -4(%ebp) 
< 	movl	-4(%ebp), %eax
< 	pushl	%eax
> 	pushl	$3
< 	movl	$5, -4(%ebp) / * store in stack */

From above listing 3 it is clear that when you use setjmp and longjmp, the only automatic variables guaranteed to remain valid are those declared volatile.

# An object modified by an interrupt service routine

Interrupt service routines often set variables that are tested in main line code. One problem that arises as soon as you use interrupt is that interrupt routines need to communicate with rest of the code .A interrupt may update a variable num that is used in main line of code .An incorrect implementation of this might be:
static lon int  num ;
void interrupt update(void)
{
++num ;
}
main()
{
long  val ;
val  = num ;
while(val !=num)
val = num ;
rturn val ;
}
When compilation system execute while statement, the optimizer in compiler may notice that it read the value num once already and that value is still in the register. Instead of re reading the value from memory, compiler may produce code to use the (possibly messed up) value in the register defeating purpose of original C program. Some compiler may optimize entire while loop assuming that since the value of num was just assigned to val , the two must be equal and condition in while statement will therefore always be false .To avoid this ,you have to declare num to be volatile that warns compilers that certain variables may change because of interrupt routines.
static volatile  long int num ;
With volatile keyword in the declaration the compiler knows that the value of num must b read from memory every time it is referenced.

Conclusion

When using exact semantics is necessary, volatile should be used. If you are given a piece of touchy code given as above. It is a good idea in any case to look the compiler outputting listing at the assembly language to be sure that compiler produce code that makes sense.
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值