搜索
bottom↓
回复: 59
打印 上一主题 下一主题

[转载][翻译]Introduction to the Volatile Keyword(认识关键字volatile)

[复制链接]

出0入296汤圆

跳转到指定楼层
1
发表于 2007-2-9 10:50:19 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
Introduction to the Volatile Keyword

认识关键字Volatile



The use of volatile is poorly understood by many programmers. This is not surprising, as most C texts dismiss it in a sentence or two.

很多程序员对于volatile的用法都不是很熟悉。这并不奇怪,很多介绍C语言的书籍对于他的用法都闪烁其辞。



Have you experienced any of the following in your C/C++ embedded code?

•        Code that works fine-until you turn optimization on

•        Code that works fine-as long as interrupts are disabled

•        Flaky hardware drivers

•        Tasks that work fine in isolation-yet crash when another task is enabled

在你们使用C/C++语言开发嵌入式系统的时候,遇到过以下的情况么?

•        一打开编译器的编译优化选项,代码就不再正常工作了;

•        中断似乎总是程序异常的元凶;

•        硬件驱动工作不稳定;

•        多任务系统中,单个任务工作正常,加入任何其他任务以后,系统就崩溃了。



If you answered yes to any of the above, it's likely that you didn't use the C keyword volatile. You aren't alone. The use of volatile is poorly understood by many programmers. This is not surprising, as most C texts dismiss it in a sentence or two.

如果你曾经向别人请教过和以上类似的问题,至少说明,你还没有接触过C语言关键字volatile的用法。这种情况,你不是第一个遇到。很多程序员对于volatile都几乎一无所知。大部分介绍C语言的文献对于它都闪烁其辞。



volatile is a qualifier that is applied to a variable when it is declared. It tells the compiler that the value of the variable may change at any time-without any action being taken by the code the compiler finds nearby. The implications of this are quite serious. However, before we examine them, let's take a look at the syntax.

Volatile是一个变量声明限定词。它告诉编译器,它所修饰的变量的值可能会在任何时刻被意外的更新,即便与该变量相关的上下文没有任何对其进行修改的语句。造成这种“意外更新”的原因相当复杂。在我们分析这些原因之前,我们先回顾一下与其相关的语法。





Syntax

语法



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

要想给一个变量加上volatile限定,只需要在变量类型声明附之前/后加入一个volatile关键字就可以了。下面的两个实例是等效的,它们都是将foo声明为一个“需要被实时更新”的int型变量。

volatile int foo;

int volatile foo;



Now, it turns out that pointers to volatile variables are very common. Both of these declarations declare foo to be a pointer to a volatile integer:

同样,声明一个指向volatile型变量的指针也是非常类似的。下面的两个声明都是将foo定义为一个指向volatile integer型变量的指针。

volatile int * foo;

int volatile * foo;



Volatile pointers to non-volatile variables are very rare (I think I've used them once), but I'd better go ahead and give you the syntax:

一个Volatile型的指针指向一个非volatile型变量的情况非常少见(我想,我可能使用过一次),尽管如此,我还是要给出他的语法:

int * volatile foo;



And just for completeness, if you really must have a volatile pointer to a volatile variable, then:

最后一种形式,针对你真的需要一个volatile型的指针指向一个volatile型的情形:

int volatile * volatile foo;



Incidentally, for a great explanation of why you have a choice of where to place volatile and why you should place it after the data type (for example, int volatile * foo), consult Dan Sak's column, "Top-Level cv-Qualifiers in Function Parameters" (February 2000, p. 63).

顺便说一下,如果你想知道关于“我们需要在什么时候在什么地方使用volatile”和“为什么我们需要volatile放在变量类型后面(例如,int volatile * foo)”这类问题的详细内容,请参考Dan Sak`s的专题,“Top-Level cv-Qualifiers in Function Parameters”。



Finally, 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.

最后,如果你将volatile应用在结构体或者是公用体上,那么该结构体/公用体内的所有内容就都带有volatile属性了。如果你并不想这样(牵一发而动全身),你可以仅仅在结构体/公用体中的某一个成员上单独使用该限定。





Use

使用



A variable should be declared volatile whenever its value could change unexpectedly. In practice, only three types of variables could change:

•        Memory-mapped peripheral registers

•        Global variables modified by an interrupt service routine

•        Global variables within a multi-threaded application

当一个变量的内容可能会被意想不到的更新时,一定要使用volatile来声明该变量。通常,只有三种类型的变量会发生这种“意外”:

•        在内存中进行地址映射的设备寄存器;

•        在中断处理程序中可能被修改的全局变量;

•        多线程应用程序中的全局变量;





Peripheral registers

设备寄存器



Embedded systems contain real hardware, usually with sophisticated peripherals. These peripherals contain registers whose values may change asynchronously to the program flow. As a very simple example, consider an 8-bit status register at address 0x1234. It is required that you poll the status register until it becomes non-zero. The nave and incorrect implementation is as follows:

嵌入式系统的硬件实体中,通常包含一些复杂的外围设备。这些设备中包含的寄存器,其值往往随着程序的流程同步的进行改变。在一个非常简单的例子中,假设我们有一个8位的状态寄存器映射在地址0x1234上。系统需要我们一直监测状态寄存器的值,直到它的值不为0为止。通常错误的实现方法是:

UINT1 * ptr = (UINT1 *) 0x1234;

// Wait for register to become non-zero.等待寄存器为非0值

while (*ptr == 0);

// Do something else.作其他事情





This will almost certainly fail as soon as you turn the optimizer on, since the compiler will generate assembly language that looks something like this:

一旦你打开了优化选项,这种写法肯定会失败,编译器就会生成类似如下的汇编代码:

    mov    ptr, #0x1234     mov    a, @ptr loop     bz    loop





The rationale of the optimizer is quite simple: having already read the variable's value into the accumulator (on the second line), there is no need to reread it, since the value will always be the same. Thus, in the third line, we end up with an infinite loop. To force the compiler to do what we want, we modify the declaration to:

优化的工作原理非常简单:一旦我们我们将一个变量读入寄存器中(参照代码的第二行),如果(从变量相关的上下文看)变量的值总是不变的,那么就没有必要(从内存中)从新读取他。在代码的第三行中,我们使用一个无限循环来结束。为了强迫编译器按照我们的意愿进行编译,我们修改指针的声明为:

UINT1 volatile * ptr =

    (UINT1 volatile *) 0x1234;

The assembly language now looks like this:

对应的汇编代码为:

    mov     ptr, #0x1234

loop    mov    a, @ptr        

    bz    loop

The desired behavior is achieved.

我们需要的功能实现了!





Subtler problems tend to arise with registers that have special properties. For instance, a lot of peripherals contain registers that are cleared simply by reading them. Extra (or fewer) reads than you are intending can cause quite unexpected results in these cases.

对于一些较为特殊的寄存器,(我们上面提到的方法)会导致一些难以想象的错误。事实上,很多设备寄存器在读取一次以后就会被清除。这种情况下,多余的读取操作会导致意想不到的错误。





Interrupt service routines

中断处理程序



Interrupt service routines often set variables that are tested in main line code. For example, a serial port interrupt may test each received character to see if it is an ETX character (presumably signifying the end of a message). If the character is an ETX, the ISR might set a global flag. An incorrect implementation of this might be:

中断处理程序经常负责更新一些在主程序中被查询的变量的值。例如,一个串行通讯中断会检测接收到的每一个字节是否为ETX信号(以便来确认一个消息帧的结束标志)。如果其中的一个字节为ETX,中断处理程序就是修改一个全局标志。一个错误的实现方法可能为:

int etx_rcvd = FALSE;

void main()

{

    ...

    while (!ext_rcvd)

    {

        // Wait

    }

    ...

}

interrupt void rx_isr(void)

{

    ...

    if (ETX == rx_char)

    {

        etx_rcvd = TRUE;

    }

    ...

}





With optimization turned off, this code might work. However, any half decent optimizer will "break" the code. The problem is that the compiler has no idea that etx_rcvd can be changed within an ISR. As far as the compiler is concerned, the expression !ext_rcvd is always true, and, therefore, you can never exit the while loop. Consequently, all the code after the while loop may simply be removed by the optimizer. If you are lucky, your compiler will warn you about this. If you are unlucky (or you haven't yet learned to take compiler warnings seriously), your code will fail miserably. Naturally, the blame will be placed on a "lousy optimizer."

在编译优化选项关闭的时候,代码可能会工作的很好。但是,即便是任何半吊子的优化,也会“破坏”这个代码的意图。问题就在于,编译器并不知道etx_rcvd会在中断处理程序中被更新。在编译器可以检测的上下文内,表达式!ext_rcvd总是为真,所以,你就永远无法从循环中跳出。因此,该循环后面的代码会被当作“不可达到 ”的内容而被编译器的优化选项简单的删除掉。如果你比较幸运,你的编译器也许会给你一个相关的警告;如果你没有那么幸运(或者你没有注意到这些警告),你的代码就会导致严重的错误。通常,就会有人抱怨“该死的优化选项”。





The solution is to declare the variable etx_rcvd to be volatile. Then all of your problems (well, some of them anyway) will disappear.

解决这个问题的方法很简单:将变量etx_rcvd声明为volatile。然后,所有的(至少是一部分症状)那些错误症状就会消失。





Multi-threaded applications

多线程应用程序





Despite the presence of queues, pipes, and other scheduler-aware communications mechanisms in real-time operating systems, it is still fairly common for two tasks to exchange information via a shared memory location (that is, a global). When you add a pre-emptive scheduler to your code, your compiler still has no idea what a context switch is or when one might occur. Thus, another task modifying a shared global is conceptually identical to the problem of interrupt service routines discussed previously. So all shared global variables should be declared volatile. For example:

在实时操作系统中,除去队列、管道以及其他调度相关的通讯结构,在两个任务之间采用共享的内存空间(就是全局共享)实现数据的交换仍然是相当常见的方法。当你将一个优先权调度器应用于你的代码时,编译器仍然不知道某一程序段分支选择的实际工作方式以及什么时候某一分支情况会发生。这是因为,另外一个任务修改一个共享的全局变量在概念上通常和前面中断处理程序中提到的情形是一样的。所以,(这种情况下)所有共享的全局变量都要被声明为volatile。例如:

int cntr;

void task1(void)

{

    cntr = 0;

    while (cntr == 0)

    {

        sleep(1);

    }

    ...

}

void task2(void)

{

    ...

    cntr++;

    sleep(10);

    ...

}





This code will likely fail once the compiler's optimizer is enabled. Declaring cntr to be volatile is the proper way to solve the problem.

一旦编译器的优化选项被打开,这段代码的执行通常会失败。将cntr声明为volatile是解决问题的好办法。





Final thoughts

反思



Some compilers allow you to implicitly declare all variables as volatile. Resist this temptation, since it is essentially a substitute for thought. It also leads to potentially less efficient code.

一些编译器允许我们隐含的声明所有的变量为volatile。最好抵制这种便利的诱惑,因为它很容易让我们“不动脑子”,而且,这也常常会产生一个效率相对较低的代码。





Also, resist the temptation to blame the optimizer or turn it off. Modern optimizers are so good that I cannot remember the last time I came across an optimization bug. In contrast, I come across failures to use volatile with depressing frequency.

所以,我们又诅咒编译优化或者简单的关掉这一选项来抵制这些诱惑。现在的编译优化已经相当聪明,我不记得在编译优化中找到过什么错误。与之相比,为了解决一些错误,我却常常使用疯狂数量的volatile。





If you are given a piece of flaky code to "fix," perform a grep for volatile. If grep comes up empty, the examples given here are probably good places to start looking for problems.

如果你恰巧有一段代码需要去修正,先搜索一下有没有volatile关键字。如果找不到volatile,那么这个代码很可能会是一个很好的实例来检测前面提到过的各种错误。





Nigel Jones is a consultant living in Maryland. When not underwater, he can be found slaving away on a diverse range of embedded projects. He can be reached at NAJones@compuserve.com.

Nigel Jones 在马里兰从事顾问工作。除了为各类嵌入式项目开发充当顾问,他平时的一大爱好就是潜水。你可以通过发送邮件到NAJones@compuserve.com与其取得联系。





(本文为学习交流之用,不可以用作商业用途,版权为原作者所有,翻译 傻孩子 2007年2月。)

------------------------------------------------------------------------

水平有限,多多包涵。

阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!

出0入296汤圆

2
 楼主| 发表于 2007-2-9 10:51:11 | 只看该作者
我制作的PDF版本



单击这里下载

出0入0汤圆

3
发表于 2007-2-9 10:56:16 | 只看该作者
不错,顶个

出0入0汤圆

4
发表于 2007-2-9 11:00:09 | 只看该作者
翻译得很有水平,帮顶一下!

出0入0汤圆

5
发表于 2007-2-13 11:23:36 | 只看该作者
谢谢!辛苦了!

出0入0汤圆

6
发表于 2007-2-13 12:25:35 | 只看该作者
确实很少人搞明白这个的,当然包括我。

谢谢啦!

出0入0汤圆

7
发表于 2008-6-6 12:55:23 | 只看该作者
不错,谢谢

出0入0汤圆

8
发表于 2008-6-8 21:53:13 | 只看该作者
今天才发现,真有点后悔

出0入0汤圆

9
发表于 2008-6-8 23:46:14 | 只看该作者
精品……

出0入0汤圆

10
发表于 2008-6-17 08:41:24 | 只看该作者
.

出0入0汤圆

11
发表于 2008-6-17 15:34:29 | 只看该作者
謝謝.

出0入0汤圆

12
发表于 2008-6-17 21:11:42 | 只看该作者
学习

出0入0汤圆

13
发表于 2008-7-17 18:37:17 | 只看该作者
标记一下

出0入0汤圆

14
发表于 2008-7-17 19:10:02 | 只看该作者
以前用iar没事,昨天用gcc就遇到这个问题了。找到的资料

点击此处下载 ourdev_350492.pdf(文件大小:69K) (原文件名:volatile关键字的应用.pdf)

出0入0汤圆

15
发表于 2008-7-17 19:11:19 | 只看该作者
谢谢傻孩子!
我认识给你更深了!

出0入0汤圆

16
发表于 2008-12-23 23:36:07 | 只看该作者
mark

出0入0汤圆

17
发表于 2008-12-28 17:25:29 | 只看该作者
傻孩子的一席话真是雪中送炭呀!以前完ICC根本就不知道还有这么一回事,受益匪浅!感谢傻孩子!

出0入0汤圆

18
发表于 2009-10-14 22:09:56 | 只看该作者
顶啊

出0入0汤圆

19
发表于 2010-2-9 10:40:01 | 只看该作者
Mark

出0入0汤圆

20
发表于 2010-3-23 16:48:01 | 只看该作者
谢谢你啊,

出0入0汤圆

21
发表于 2010-3-23 16:49:38 | 只看该作者
谢谢

出0入0汤圆

22
发表于 2010-3-23 17:46:34 | 只看该作者
mark

出0入0汤圆

23
发表于 2010-4-29 12:51:25 | 只看该作者
唉,高手还是新手一看对这些关键字的使用情况就看出来了
不过像我这样的新手写代码还没用过这样的键字
学习
强大起来

出0入0汤圆

24
发表于 2010-7-3 14:43:41 | 只看该作者
mark

出0入0汤圆

25
发表于 2011-1-6 09:57:24 | 只看该作者
学习了,翻译的不错!

出0入0汤圆

26
发表于 2011-1-7 18:24:44 | 只看该作者
一句话:容易被其他变量,外部修改的变量,最好使用Volatile避免被优化。

比如外部端口的数据,公共变量。

被编译器优化的原因是:编译器只看本函数内部的代码。内部代码没有修改,直接使用前面的结果,其实在外部被修改了。

Volatile的意思其实应该是避免优化这个。

出0入0汤圆

27
发表于 2011-1-8 06:47:41 | 只看该作者
谢谢 傻孩子哈哈

出0入0汤圆

28
发表于 2011-1-24 10:50:16 | 只看该作者
请问上述翻译的原文是出自哪本书?

出0入0汤圆

29
发表于 2011-1-25 16:55:49 | 只看该作者
mark

出0入0汤圆

30
发表于 2011-1-25 17:48:41 | 只看该作者
标记下

出0入0汤圆

31
发表于 2011-1-25 20:00:33 | 只看该作者
学习了!

出0入0汤圆

32
发表于 2011-1-26 08:56:52 | 只看该作者
关键字很重要,mark

出0入0汤圆

33
发表于 2011-1-26 11:17:51 | 只看该作者
mark and learn

出0入0汤圆

34
发表于 2011-1-26 11:38:56 | 只看该作者
mark

出0入0汤圆

35
发表于 2011-5-9 11:29:53 | 只看该作者
mark
头像被屏蔽

出0入0汤圆

36
发表于 2011-6-8 00:15:20 | 只看该作者
mark

出0入0汤圆

37
发表于 2011-9-21 09:21:16 | 只看该作者
回复【13楼】xiaorunyi 萧润逸
-----------------------------------------------------------------------

这个也很好,顶

出0入0汤圆

38
发表于 2011-9-21 09:46:32 | 只看该作者
信达雅!

出0入0汤圆

39
发表于 2011-10-16 10:36:12 | 只看该作者
了解了

出0入0汤圆

40
发表于 2011-10-26 12:09:54 | 只看该作者
MARK

出0入0汤圆

41
发表于 2011-11-11 10:41:10 | 只看该作者
mark

出0入131汤圆

42
发表于 2012-12-22 20:48:31 | 只看该作者
好文章,有段时间用gcc开优化,就出现了这个问题,查了一天 才搞清楚怎么回事。从此对volatile关键字异常深刻

出0入0汤圆

43
发表于 2012-12-23 09:11:22 来自手机 | 只看该作者
翻译的好

出0入0汤圆

44
发表于 2012-12-23 09:59:40 来自手机 | 只看该作者
好东西。。

出0入0汤圆

45
发表于 2012-12-23 16:41:54 | 只看该作者
清楚,明白了

出0入0汤圆

46
发表于 2012-12-25 18:48:00 | 只看该作者
加深认识了!

出0入0汤圆

47
发表于 2013-1-29 20:53:51 来自手机 | 只看该作者
中英对照,不错

出0入0汤圆

48
发表于 2013-8-20 16:05:33 | 只看该作者
很多面试嵌入式的公司都要问这个问题

出0入0汤圆

49
发表于 2013-11-14 21:34:20 | 只看该作者
的确不太了解

出0入0汤圆

50
发表于 2016-3-5 00:59:13 | 只看该作者
表示我的英文要不断提高才行啊。顺便问问傻孩子,你是怎么学英文的???

出0入0汤圆

51
发表于 2016-3-5 11:51:22 | 只看该作者
上传楼主文中提到的《Top-Level cv-Qualifiers in Function Parameters》


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

52
发表于 2016-4-3 13:41:26 | 只看该作者
谢谢分享   最近就遇到了这个问题

出0入0汤圆

53
发表于 2016-4-3 17:18:50 | 只看该作者
outt60777 发表于 2011-1-7 18:24
一句话:容易被其他变量,外部修改的变量,最好使用Volatile避免被优化。

比如外部端口的数据,公共变量。 ...

一语中的

出0入0汤圆

54
发表于 2016-4-3 18:28:31 | 只看该作者
谢谢分享

出0入0汤圆

55
发表于 2016-4-4 11:35:55 | 只看该作者
厉害厉害

出0入42汤圆

56
发表于 2016-4-12 18:14:22 | 只看该作者
谢谢分享,学习一下

出0入0汤圆

57
发表于 2016-4-12 21:51:11 | 只看该作者
我现在对这个关键字简而言之就是防止变量被编译器优化。什么随存随取。

出0入0汤圆

58
发表于 2016-4-16 08:46:01 | 只看该作者
赞一个。很期待傻孩子的新作~

出0入0汤圆

59
发表于 2016-4-16 09:09:26 | 只看该作者
mark下,学习

出0入0汤圆

60
发表于 2016-4-16 12:34:18 | 只看该作者
的确很少用这个关键字,先mark下,感觉还是蛮重要的
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-27 06:41

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表