C语言中的volatile关键字

更新时间:2023-04-11 16:16:01 阅读量: 实用文档 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

C语言中的volatile关键字

volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

使用该关键字的例子如下:

int volatile nVint;

当要求使用volatile声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。

例如:

volatile int i=10;

int a = i;

...

//其他代码,并未明确告诉编译器,对i进行过操作

int b = i;

volatile指出i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在b中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问。

××××××××××××××××××××××××××××××××××

××××××××

关键字volatile有什么含意?并给出三个不同的例子。

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编

译器就不会去假设这

个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重

新读取这个变量的值,

而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:

1). 并行设备的硬件寄存器(如:状态寄存器)

2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)

3). 多线程应用中被几个任务共享的变量

回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系

统程序员的最基本

的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都

要求volatile变量。

不懂得volatile内容将会带来灾难。

假设被面试者正确地回答了这是问题(嗯,怀疑这否会是这样),我将稍微深究

一下,看一下这

家伙是不是直正懂得volatile完全的重要性。

1). 一个参数既可以是const还可以是volatile吗,解释为什么。

2). 一个指针可以是volatile 吗,解释为什么。

3). 下面的函数有什么错误:

int square(volatile int *ptr)

{

return *ptr * *ptr;

}

下面是答案:

1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。

它是const因为程序不应该试图去修改它。(也就是说,const指定了我们的程序代码中是不可

以改变这个变量的,但是volatile指出,可以是由于硬件的原因,在代码意外更改这个值,但是我

们的代码同时会更新使用这个最新的数值)

2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指

针时。(把指针声明为volatile的类型,可以保证指针所指向的地址随时发生变化)

3). 这段代码的有个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr

指向一个volatile型参数,编译器将产生类似下面的代码:

int square(volatile int *ptr)

{

int a,b;

a = *ptr;

b = *ptr;

return a * b;

}

由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是

你所期望的平方值~正确的代码如下:

long square(volatile int *ptr)

{

int a;

a = *ptr;

return a * a;

}

Do you volatile? should you?

by Dr. Kevin P. Dankwardt

Volatile is an ANSI C type modifier that is frequently needed in C code that is

part of signal/interrupt handlers, threaded code, and other kernel code,

including device drivers. In general, any data that may be undated asynchronously should be declared to be volatile. Incidentally, this issue

is not related to CPU caches, except that

re-loading of variables into registers may involve cache hits or misses.

Why Use Volatile?

The reason to use volatile is to ensure that the compiler generates code to re-load a data item each time it is referenced in your program.

Without volatile, the compiler may generate code that merely re-uses the value it already loaded into a register.

Volatile advises the compiler that the data may be modified in a manner that may not be determinable by the compiler. This could be, for example, when a pointer is mapped to a device's hardware registers. The device may independently change the values unbeknownst to the compiler.

With gcc the -O2 option is normally required to see the effect of

not using volatile. Without -O2 or greater optimization, the compiler is likely to re-load registers each time a variable is referenced, anyway. Don't blame the optimizer if a program gets incorrect results because the program does not use volatile where required.

For example, if two threads share a variable, sum, and one or both threads modify it, then the other thread may use a stale value in a register instead of going back to memory to get the new value. Instead, each time the thread references sum, it must be re-loaded. The way to insure this occurs in ANSI C is to declare sum to be volatile.

Example 1.

// Program to measure the difference between volatile and not

// written by Kevin P. Dankwardt k@cb85440c3b68011ca300a6c30c2259010202f3a3

// 3 March 2005

#include

#include

#include

#include

#ifndef VOLATILE

#define VOLATILE

#endif

VOLATILE int total=0;

void handle(int signo)

{

if (signo == SIGALRM)

{

printf("Total = %d\n",total);

exit(0);

}

total++;

}

int main ()

{

VOLATILE unsigned x=0;

VOLATILE int i,j;

struct sched_param param;

struct itimerval val;

val.it_cb85440c3b68011ca300a6c30c2259010202f3a3_sec = 0;

val.it_cb85440c3b68011ca300a6c30c2259010202f3a3_usec = 10000; // 10 ms val.it_cb85440c3b68011ca300a6c30c2259010202f3a3_sec = 0;

val.it_cb85440c3b68011ca300a6c30c2259010202f3a3_usec = 10000; // 10 ms setitimer(ITIMER_VIRTUAL, &val, NULL);

param.sched_priority = 99;

if (sched_setscheduler(0, SCHED_RR, ¶m) ==-1) {

perror("setting priority");

exit(1);

}

#define BIGNUM (1000)

alarm(10);

signal(SIGALRM,handle);

signal(SIGVTALRM,handle);

while (total < 100 )

{

for (i=0; i

{

for (j=0; j

x = x+j ;

}

}

printf("x = %u\n",x); // so optimizers doesn't throw away the loop printf("total = %d\n",total);

}

The use of volatile can be required to get correct answers. For example the program wrong will give incorrect results when it is compiled -O2 and without volatile. This slightly obtuse program is designed to stop after 100 ticks of an interval timer that ticks at

100Hz and print the value of the variable total. The tick count is incremented in the signal handler. When the count gets to 100, the program should terminate. If the tick count does not get to 100 within 10 seconds then an alarm goes off and the program terminates.

By compiling the program as:

gcc -O2 -DVOLATILE=volatile wrong.c -o wrong_v

you will see, (unless your program is preempted for quite a while), that the count gets to 100 and the program terminates as designed. With the program compiled as

gcc -O2 wrong.c -o wrong_nv

you will see, that the count becomes greater than 100 as shown when the handler prints it, but, the while loop does not terminate.

Incidentally, attempts to determine what is happening may thwart your efforts.

For example, a function call, such as to printf(), or the use of a breakpoint, in the loop, will likely spill and re-load the registers.

Syntax

The keyword volatile is similar to the const keyword. Volatile is used to modify a type. Thus an int, const int, pointer, etc. may be declared to be volatile. In addition, a point may be declared to be a pointer to volatile. A pointer to volatile means that the data to which the pointer refers is volatile as opposed to the pointer itself. Of course, both the pointer and to which it refers, may be declared to be volatile.

To declare a volatile int do:

volatile int v;

and to declare vp to be a pointer to a volatile int do:

volatile int *vp;

Since deciphering C declarations can be difficult you may want to consult the C declaration chapter in the Sun manual. This manual references the Decoder Flowchart that can be used to help decipher declarations.

In addition, Linux may have the cdecl(1) program that can be used to translate C declarations to English, as for example, in

echo 'explain volatile int *v' | cdecl

which will answer with

declare v as a pointer to volatile int

Reading C declarations is made simpler when you realize that they

are written boustrophedonically. Of course, even knowing the definition of

boustrophedonically doesn't really help. The idea is that C declarations are interpreted based on the tricky precedence of operators such as "*", "[]", and "()".

Performance Issues

In some sense, volatile is the opposite of register. Thus, one can expect to lose performance. This means don't use volatile when it is not needed. Example 2.

// Program to measure the difference between volatile and not

// written by Kevin P. Dankwardt k@cb85440c3b68011ca300a6c30c2259010202f3a3

// 3 March 2005

// compile with -O2 flag to see volatile make a difference // compile with -DVOLATILE=volatile to use volatile.

// 3 March 2005

#include

#include

#include

int total=0;

#ifndef VOLATILE

#define VOLATILE

#endif

void handle(int signo)

{

int t=total;

char type[100];

printf("%d\n", t);

exit(0);

}

int main ()

{

VOLATILE int x=0;

VOLATILE int i,j;

struct sched_param param;

param.sched_priority = 99;

if (sched_setscheduler(0, SCHED_RR, ¶m) ==-1) {

perror("setting priority");

exit(1);

}

#define BIGNUM (1span>

alarm(10);

signal(SIGALRM,handle);

for (i=0; i

{

total++;

for (j=0; j

x = x+j ;

}

printf("x = %d\n",x); // so optimizers doesn't throw away the loop }

In our performance example we can see the difference that volatile may make. If we compile this program with and without VOLATILE defined as volatile we see an average number of iterations of almost 5,000 for the volatile case and almost 20,000 for the non-volatile case. Yikes! Remember that we must compile both of them with the -O2 option. (These iteration counts were made on a 400Mhz AMD-K6.)

Linux Examples

The use of the volatile keyword is common in the Linux kernel source. For example, of the 10,607 .c and .h files in the Fedora Core 1, Linux kernel source directory, 1,694 have the string "volatile" in them somewhere. As an example, the file drivers/net/eepro.c uses volatile in three places.

385: volatile s32 cmd_status; /* All command and status fields. */ 392: volatile s32 status;

764: volatile s32 *self_test_results;

Generated Code

By examining the code generated by the compiler one can see the difference volatile makes. In this simple example we can see the x86 assembly language when volatile is used and when volatile is not used.

Quiz Yourself

What is volatile in each of the following examples? Are they all

legal declarations?

1) volatile int *s1;

2) int* volatile s2;

3) volatile int* volatile s3;

4) const volatile int * volatile s4;

5) volatile int * (*f)(volatile int *);

Check your answers.

Summary

The volatile keyword is relatively unknown. There are times when its use is required for correct operation of C/C++ programs. In general,

whenever a variable may be altered asynchronously, such as by a signal handler or mapped hardware, the variable must be declared to be volatile.

Since volatile prevents re-using values in registers, volatile comes with a performance penalty that can be substantial.

Also, since declarations involving volatile can be difficult to decipher you may want to use cdecl(1).

本文来源:https://www.bwwdw.com/article/77pl.html

Top