当前位置:首页 > volatile
  • 不要再误解C volatile了

    作者:Liam Huang 最近在讨论多线程编程中的一个可能的 false sharing 问题时,有人提出加 volatile 可能可以解决问题。这种错误的认识荼毒多年,促使我写下这篇文章。 约定 Volatile 这个话题,涉及到计算机科学多个领域多个层次的诸多细节。仅靠一篇博客,很难穷尽这些细节。因此,若不对讨论范围做一些约定,很容易就有诸多漏洞。到时误人子弟,就不好了。以下是一些基本的约定: 1. 这篇博文讨论的 volatile 关键字,是 C 和 C 语言中的关键字。Java 等语言中,也有 volatile 关键字。但它们和 C/C 里的 volatile 不完全相同,不在这篇博文的讨论范围内。 2. 这篇博文讨论的 volatile 关键字,是限定在 C/C 标准之下的。这也就是说,我们讨论的内容应该是与平台无关的,同时也是与编译器扩展无关的。 3. 相应的,这篇文章讨论的「标准」指的是 C/C 的标准,而不是其他什么东西。 4. 我们希望编写的代码是 (1) 符合标准的,(2) 性能良好的,(3) 可移植的。这里 (1) 保证了代码执行结果的正确性,(2) 保证了高效性,(3) 体现了平台无关性(以及编译器扩展等的无关性)。 含义 单词 volatile 的含义 在谈及 C/C 中的 volatile 关键字时,总有人会拿 volatile 这个英文单词的中文解释说事。他们把 volatile 翻译作「易变的」。但事实上,对于翻译来说,很多时候目标语言很难找到一个词能够反映源语言中单词的全部含义和细节。此处「易变的」就无法做到这一点。 Volatile 的意思,若要详细理解,还是应该查阅权威的英英字典。在柯林斯高阶学习词典中,volatile 是这样解释的: A situation that is volatile is likely to change suddenly and unexpectedly. 这里对 volatile 的解释有三个精髓的形容词和副词,体现了 volatile 的含义。 1. likely:可能的。这意味着被 volatile 形容的对象「有可能也有可能不」发生改变,因此我们不能对这样的对象的状态做出任何假设。 2. suddenly:突然地。这意味着被 volatile 形容的对象可能发生瞬时改变。 3. unexpectedly:不可预期地。这与 likely 相互呼应,意味着被 volatile 形容的对象可能以各种不可预期的方式和时间发生更改。 因此,volatile 其实就是告诉我们,被它修饰的对象出现任何情况都不要奇怪,我们不能对它们做任何假设。 程序中 volatile 的含义 对于程序员来说,程序本身的任何行为都必须是可预期的。那么,在程序当中,什么才叫 volatile 呢?这个问题的答案也很简单:程序可能受到程序之外的因素影响。 考虑以下 C/C 代码。 volatile int *p = /* ... */;int a, b;a = *p;b = *p; 若忽略 volatile,那么 p 就只是一个「指向 int 类型的指针」。这样一来,a = *p; 和 b = *p; 两句,就只需要从内存中读取一次就够了。因为从内存中读取一次之后,CPU 的寄存器中就已经有了这个值;把这个值直接复用就可以了。这样一来,编译器就会做优化,把两次访存的操作优化成一次。这样做是基于一个假设:我们在代码里没有改变 p 指向内存地址的值,那么这个值就一定不会发生改变。 此处说的「读取内存」,包括了读取 CPU 缓存和读取计算机主存。 然而,由于 MMIP(Memory mapped I/O)的存在,这个假设不一定是真的。例如说,假设 p 指向的内存是一个硬件设备。这样一来,从 p 指向的内存读取数据可能伴随着可观测的副作用:硬件状态的修改。此时,代码的原意可能是将硬件设备返回的连续两个 int 分别保存在 a 和 b 当中。这种情况下,编译器的优化就会导致程序行为不符合预期了。 总结来说,被 volatile 修饰的变量,在对其进行读写操作时,会引发一些可观测的副作用。而这些可观测的副作用,是由程序之外的因素决定的。 关键字 volatile 的含义 CPP reference 网站是对 C 和 C 语言标准的整理。因此,绝大多数时候,我们可以通过这个网站对语言标准进行查询。关于 volatile 关键字,有 C 语言标准和 C 语言标准可查。这里摘录两份标准对 volatile 访问的描述。 C 语言:Every access (both read and write) made through an lvalue expression of volatile-qualified type is considered an observable side effect for the purpose of optimization and is evaluated strictly according to the rules of the abstract machine (that is, all writes are completed at some time before the next sequence point). This means that within a single thread of execution, a volatile access cannot be optimized out or reordered relative to another visible side effect that is separated by a sequence point from the volatile access. C 语言:Every access (read or write operation, member function call, etc.) made through a glvalue expression of volatile-qualified type is treated as a visible side-effect for the purposes of optimization (that is, within a single thread of execution, volatile accesses cannot be optimized out or reordered with another visible side effect that is sequenced-before or sequenced-after the volatile access. This makes volatile objects suitable for communication with a signal handler, but not with another thread of execution, see std::memory_order). Any attempt to refer to a volatile object through a non-volatile glvalue (e.g. through a reference or pointer to non-volatile type) results in undefined behavior. 这里首先解释两组概念:值类型和序列点(执行序列)。 值类型指的是左值(lvalue)右值(rvalue)这些概念。关于左值和右值,前作有过介绍。简单的理解,左值可以出现在赋值等号的左边,使用时取的是作为对象的身份;右值不可以出现在赋值等号的左边,使用时取的是对象的值。除了 lvalue 和 rvalue,C 还定义了其他的值类型。其中,xvalue 大体可以理解为返回右值引用的函数调用或表达式,而 glvalue 则是 lvalue 和 xvalue 之和。 序列点则是 C/C 中讨论执行顺序时会提到的概念。对于 C/C 的表达式来说,执行表达式有两种类型的动作:(1) 计算某个值、(2) 副作用(例如访问 volatile 对象,原子同步,修改文件等)。因此,如果在两个表达式 E1 和 E2 中间有一个序列点,或者在 C 中 E1 于序列中在 E2 之前,则 E1 的求值动作和副作用都会在 E2 的求值动作和副作用之前。关于序列点和序列顺序规则,可以参考:这里和这里。 因此我们讲,在 C/C 中,对 volatile 对象的访问,有编译器优化上的副作用: 1. 不允许被优化消失(optimized out); 2. 于序列上在另一个对 volatile 对象的访问之前。 这里提及的「不允许被优化」表示对 volatile 变量的访问,编译器不能做任何假设和推理,都必须按部就班地与「内存」进行交互。因此,上述例中「复用寄存器中的值」就是不允许的。 需要注意的是,无论是 C 还是 C 的标准,对于 volatile 访问的序列性,都有单线程执行的前提。其中 C 标准特别提及,这个顺序性在多线程环境里不一定成立。 volatile 与多线程 volatile 可以解决多线程中的某些问题,这一错误认识荼毒多年。例如,在知乎「volatile」话题下的介绍就是「多线程开发中保持可见性的关键字」。为了拨乱反正,这里先给出结论(注意这些结论都基于本文第一节提出的约定之上): 1. volatile 不能解决多线程中的问题。 2. 按照 Hans Boehm

    时间:2021-09-14 关键词: volatile

  • 不要再误解C  volatile了

    关注、星标公众号,直达精彩内容来源:https://liam.page/2018/01/18/volatile-in-C-and-Cpp/作者:Liam Huang最近在讨论多线程编程中的一个可能的 false sharing 问题时,有人提出加 volatile 可能可以解决问题。这种错误的认识荼毒多年,促使我写下这篇文章。约定Volatile 这个话题,涉及到计算机科学多个领域多个层次的诸多细节。仅靠一篇博客,很难穷尽这些细节。因此,若不对讨论范围做一些约定,很容易就有诸多漏洞。到时误人子弟,就不好了。以下是一些基本的约定:1. 这篇博文讨论的 volatile 关键字,是 C 和 C 语言中的关键字。Java 等语言中,也有 volatile 关键字。但它们和 C/C 里的 volatile 不完全相同,不在这篇博文的讨论范围内。2. 这篇博文讨论的 volatile 关键字,是限定在 C/C 标准之下的。这也就是说,我们讨论的内容应该是与平台无关的,同时也是与编译器扩展无关的。3. 相应的,这篇文章讨论的「标准」指的是 C/C 的标准,而不是其他什么东西。4. 我们希望编写的代码是 (1) 符合标准的,(2) 性能良好的,(3) 可移植的。这里 (1) 保证了代码执行结果的正确性,(2) 保证了高效性,(3) 体现了平台无关性(以及编译器扩展等的无关性)。含义单词 volatile 的含义在谈及 C/C 中的 volatile 关键字时,总有人会拿 volatile 这个英文单词的中文解释说事。他们把 volatile 翻译作「易变的」。但事实上,对于翻译来说,很多时候目标语言很难找到一个词能够反映源语言中单词的全部含义和细节。此处「易变的」就无法做到这一点。Volatile 的意思,若要详细理解,还是应该查阅权威的英英字典。在柯林斯高阶学习词典中,volatile 是这样解释的:A situation that is volatile is likely to change suddenly and unexpectedly.这里对 volatile 的解释有三个精髓的形容词和副词,体现了 volatile 的含义。1. likely:可能的。这意味着被 volatile 形容的对象「有可能也有可能不」发生改变,因此我们不能对这样的对象的状态做出任何假设。2. suddenly:突然地。这意味着被 volatile 形容的对象可能发生瞬时改变。3. unexpectedly:不可预期地。这与 likely 相互呼应,意味着被 volatile 形容的对象可能以各种不可预期的方式和时间发生更改。因此,volatile 其实就是告诉我们,被它修饰的对象出现任何情况都不要奇怪,我们不能对它们做任何假设。程序中 volatile 的含义对于程序员来说,程序本身的任何行为都必须是可预期的。那么,在程序当中,什么才叫 volatile 呢?这个问题的答案也很简单:程序可能受到程序之外的因素影响。考虑以下 C/C 代码。volatile int *p = /* ... */;int a, b;a = *p;b = *p;若忽略 volatile,那么 p 就只是一个「指向 int 类型的指针」。这样一来,a = *p; 和 b = *p; 两句,就只需要从内存中读取一次就够了。因为从内存中读取一次之后,CPU 的寄存器中就已经有了这个值;把这个值直接复用就可以了。这样一来,编译器就会做优化,把两次访存的操作优化成一次。这样做是基于一个假设:我们在代码里没有改变 p 指向内存地址的值,那么这个值就一定不会发生改变。此处说的「读取内存」,包括了读取 CPU 缓存和读取计算机主存。然而,由于 MMIP(Memory mapped I/O)的存在,这个假设不一定是真的。例如说,假设 p 指向的内存是一个硬件设备。这样一来,从 p 指向的内存读取数据可能伴随着可观测的副作用:硬件状态的修改。此时,代码的原意可能是将硬件设备返回的连续两个 int 分别保存在 a 和 b 当中。这种情况下,编译器的优化就会导致程序行为不符合预期了。总结来说,被 volatile 修饰的变量,在对其进行读写操作时,会引发一些可观测的副作用。而这些可观测的副作用,是由程序之外的因素决定的。关键字 volatile 的含义CPP reference 网站是对 C 和 C 语言标准的整理。因此,绝大多数时候,我们可以通过这个网站对语言标准进行查询。关于 volatile 关键字,有 C 语言标准和 C 语言标准可查。这里摘录两份标准对 volatile 访问的描述。C 语言:Every access (both read and write) made through an lvalue expression of volatile-qualified type is considered an observable side effect for the purpose of optimization and is evaluated strictly according to the rules of the abstract machine (that is, all writes are completed at some time before the next sequence point). This means that within a single thread of execution, a volatile access cannot be optimized out or reordered relative to another visible side effect that is separated by a sequence point from the volatile access.C 语言:Every access (read or write operation, member function call, etc.) made through a glvalue expression of volatile-qualified type is treated as a visible side-effect for the purposes of optimization (that is, within a single thread of execution, volatile accesses cannot be optimized out or reordered with another visible side effect that is sequenced-before or sequenced-after the volatile access. This makes volatile objects suitable for communication with a signal handler, but not with another thread of execution, see std::memory_order). Any attempt to refer to a volatile object through a non-volatile glvalue (e.g. through a reference or pointer to non-volatile type) results in undefined behavior.这里首先解释两组概念:值类型和序列点(执行序列)。值类型指的是左值(lvalue)右值(rvalue)这些概念。关于左值和右值,前作有过介绍。简单的理解,左值可以出现在赋值等号的左边,使用时取的是作为对象的身份;右值不可以出现在赋值等号的左边,使用时取的是对象的值。除了 lvalue 和 rvalue,C 还定义了其他的值类型。其中,xvalue 大体可以理解为返回右值引用的函数调用或表达式,而 glvalue 则是 lvalue 和 xvalue 之和。序列点则是 C/C 中讨论执行顺序时会提到的概念。对于 C/C 的表达式来说,执行表达式有两种类型的动作:(1) 计算某个值、(2) 副作用(例如访问 volatile 对象,原子同步,修改文件等)。因此,如果在两个表达式 E1 和 E2 中间有一个序列点,或者在 C 中 E1 于序列中在 E2 之前,则 E1 的求值动作和副作用都会在 E2 的求值动作和副作用之前。关于序列点和序列顺序规则,可以参考:这里和这里。因此我们讲,在 C/C 中,对 volatile 对象的访问,有编译器优化上的副作用:1. 不允许被优化消失(optimized out);2. 于序列上在另一个对 volatile 对象的访问之前。这里提及的「不允许被优化」表示对 volatile 变量的访问,编译器不能做任何假设和推理,都必须按部就班地与「内存」进行交互。因此,上述例中「复用寄存器中的值」就是不允许的。需要注意的是,无论是 C 还是 C 的标准,对于 volatile 访问的序列性,都有单线程执行的前提。其中 C 标准特别提及,这个顺序性在多线程环境里不一定成立。volatile 与多线程volatile 可以解决多线程中的某些问题,这一错误认识荼毒多年。例如,在知乎「volatile」话题下的介绍就是「多线程开发中保持可见性的关键字」。为了拨乱反正,这里先给出结论(注意这些结论都基于本文第一节提出的约定之上):1. volatile 不能解决多线程中的问题。2. 按照 Hans Boehm

    时间:2021-09-03 关键词: volatile

  • C语言类型修饰符Volatile的使用

    volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。 volatile区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。 不懂得volatile的内容将会带来灾难。 int volatile nVint; 当要求使用volatile 声明的变量的值时,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。 由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化,对于变量, 编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在b中。而不是重新从i里面读取. 这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问。 volatile 指出 i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取. •;存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义,如并行设备的硬件寄存器(如:状态寄存器) •;中断服务程序中修改的供其它程序检测的变量需要加volatile,如一个中断服务子程序中会访问到的非自动变量(Non-automatic variables) •;多任务环境下各任务间共享的数据,如多线程应用中被几个任务共享的变量

    时间:2018-07-23 关键词: C语言 单片机制作 修饰符 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因为程序不应该试图去修改它。 2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。 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; }讲讲我的理解:关键在于两个地方: 1. 编译器的优化 (请高手帮我看看下面的理解)在本次线程内, 当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中;以后,再取变量值时,就直接从寄存器中取值;当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以便保持一致当变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致当该寄存器在因别的线程等而改变了值,原变量的值不会改变,从而造成应用程序读取的值和实际的变量值不一致举一个不太准确的例子:发薪资时,会计每次都把员工叫来登记他们的银行卡号;一次会计为了省事,没有即时登记,用了以前登记的银行卡号;刚好一个员工的银行卡丢了,已挂失该银行卡号;从而造成该员工领不到工资员工 -- 原始变量地址 银行卡号 -- 原始变量在寄存器的备份2. 在什么情况下会出现(如1楼所说)1). 并行设备的硬件寄存器(如:状态寄存器) 2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables) 3). 多线程应用中被几个任务共享的变量 补充: volatile应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人;“易变”是因为外在因素引起的,象多线程,中断等,并不是因为用volatile修饰了的变量就是“易变”了,假如没有外因,即使用volatile定义,它也不会变化;而用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可以保证对特殊地址的稳定访问。 >>>>注意,在vc6中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过插入汇编代码,测试有无volatile关键字,对程序最终代码的影响: >>>>首先,用classwizard建一个win32 console工程,插入一个voltest.cpp文件,输入下面的代码: >> #i nclude <stdio.h> void main() { int i=10; int a = i; printf("i= %d",a); //下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道 __asm { mov dword ptr [ebp-4], 20h } int b = i; printf("i= %d",b); } 然后,在调试版本模式运行程序,输出结果如下: i = 10 i = 32 然后,在release版本模式运行程序,输出结果如下: i = 10 i = 10 输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。下面,我们把 i的声明加上volatile关键字,看看有什么变化: #i nclude <stdio.h> void main() { volatile int i=10; int a = i; printf("i= %d",a); __asm { mov dword ptr [ebp-4], 20h } int b = i; printf("i= %d",b); } 分别在调试版本和release版本运行程序,输出都是: i = 10 i = 32 这说明这个关键字发挥了它的作用!------------------------------------volatile对应的变量可能在你的程序本身不知道的情况下发生改变 比如多线程的程序,共同访问的内存当中,多个程序都可以操纵这个变量 你自己的程序,是无法判定合适这个变量会发生变化 还比如,他和一个外部设备的某个状态对应,当外部设备发生操作的时候,通过驱动程序和中断事件,系统改变了这个变量的数值,而你的程序并不知道。 对于volatile类型的变量,系统每次用到他的时候都是直接从对应的内存当中提取,而不会利用cache当中的原有数值,以适应它的未知何时会发生的变化,系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。--------------------------------------------------------------------------------典型的例子 for ( int i=0; i<100000; i++); 这个语句用来测试空循环的速度的 但是编译器肯定要把它优化掉,根本就不执行 如果你写成 for ( volatile int i=0; i<100000; i++); 它就会执行了volatile的本意是“易变的” 由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如:static int i=0;int main(void) { ... while (1) { if (i) dosomething(); } }/* Interrupt service routine. */ void ISR_2(void) { i=1; }程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过i,因此 可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被 调用。如果将将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。一般说来,volatile用在如下的几个地方:1、中断服务程序中修改的供其它程序检测的变量需要加volatile;2、多任务环境下各任务间共享的标志应该加volatile;3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实 现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。

    时间:2018-06-27 关键词: 硬件 寄存器 技术教程 编译器优化 volatile

  • C语言中volatile关键字的使用

      volatile的意思是易变的、可变的,作用是限制编译器优化某些变量。首先看一段C51程序:  Keil在优化级别是为8时得到如下汇编代码(部分未列出):  可以看到,变量d的值赋给x,y,z时,只有x中是直接读取的d中数值,而y=d,z=d则直接将寄存器中的数值赋给y,z。若在此过程中,变量d的值被改变(比如d是一个硬件寄存器),则y,z变量中得到的数据将是错误的,因此在某些应用中程序存在隐患。    这类问题并不是编译器的问题。由于访问内部寄存器比访问RAM速度块,因此编译器在编译类似程序时,会对程序进行优化,除第一次编译变量所在在连续读取一个变量时,编译器为了简化程序,只要有可能就会把第一次读取的值放在ACC或Rx中,在以后的读取该变量的值时就直接使用第一次的读取值。如果该变量的值在此过程中已经被外设(如读取外部设备端口时经常将外设端口看作一外部RAM地址)或其他程序(如中断服务程序)所改变,可能就会出错。为了解决这类问题,常用的方法就是降低编译器的优化级别或者使用volatile关键字。显然降低优化级别不是所期望的,因此用volatile关键字修饰相关变量很有必要。    上文中的例子将d加上volatile关键字后,如下:  重新编译得到的代码(部分未列出)如下:  可以看这此y,z变量的值是从d的存储区中读取的。这主要是由编译器的优化早成的,而不是编译器的错误。用volatile变量对变量d修饰后,编译器不对这个变量的操作进行优化,代码的执行达到期望的目的。    一般说来,volatile关键字用在如下的几个地方。    (1)中断服务程序中修改的供其他程序检测的变量需要加volatile。    (2)多任务环境下各任务间共享的标志应该加volatile。    (3)存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同意义。

    时间:2018-06-04 关键词: 语言 关键字 volatile

  • C语言中的volatile关键字

    volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改。 用volatile关键字声明的变量i每一次被访问时,执行部件都会从i相应的内存单元中取出i的值。 没有用volatile关键字声明的变量i在被访问的时候可能直接从cpu的寄存器中取值(因为之前i被访问过,也就是说之前就从内存中取出i的值保存到某个寄存器中),之所以直接从寄存器中取值,而不去内存中取值,是因为编译器优化代码的结果(访问cpu寄存器比访问ram快的多)。 以上两种情况的区别在于被编译成汇编代码之后,两者是不一样的。之所以这样做是因为变量i可能会经常变化,保证对特殊地址的稳定访问。 volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改 ,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的 代码就不再进行优化,从而可以提供对特殊地址的稳定访问。 volatile变量可能用于如下几种情况: (1) 并行设备的硬件寄存器(如:状态寄存器,例中的代码属于此类); (2) 一个中断服务子程序中会访问到的非自动变量(也就是全局变量); (3) 多线程应用中被几个任务共享的变量。 使用该关键字的例子如下: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可以保证对特殊地址的稳定访问。 注意,在vc6中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过插入汇编代码,测试有无volatile关键字,对程序最终代码的影响: 首先,用classwizard建一个win32 console工程,插入一个voltest.cpp文件,输入下面的 代码: #i nclude <stdio.h> void main() { int i=10; int a = i; printf("i= %dn",a); //下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道 __asm { mov dword ptr [ebp-4], 20h } int b = i; printf("i= %dn",b); } 然后,在调试版本模式运行程序,输出结果如下: i = 10 i = 32 然后,在release版本模式运行程序,输出结果如下: i = 10 i = 10 输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。 下面,我们把 i的声明加上volatile关键字,看看有什么变化: #i nclude <stdio.h> void main() { volatile int i=10; int a = i; printf("i= %dn",a); __asm { mov dword ptr [ebp-4], 20h } int b = i; printf("i= %dn",b); } 分别在调试版本和release版本运行程序,输出都是: i = 10 i = 32 这说明这个关键字发挥了它的作用!来源:xinxin1次

    时间:2018-06-04 关键词: 语言 关键字 volatile

  • 嵌入式C中volatile的用法

    根据c/c++语法,const可以出现的地方,volatile几乎也都可以出现。但是,const修饰的对象其值不能改变,而volatile修饰的对象其值可以随意地改变,也就是说,volatile对象值可能会改变,即使没有任何代码去改变它。在这一点上,最典型的例子就是内存映射的设备寄存器和多线程中的共享对象。懂得使用volatile也是一门小小的艺术。使用volatile约束符可以阻止编译器对代码过分优化防止出现一些你意想不到的情况,达不到预期的结果;过频地使用volatile很可能会增加代码尺寸和降低性能。下面举个例子来说明volatile在优化中的微妙作用。 1.阻止编译器优化 ARM Evaluator-7T模拟单机板使用基于内存映射的设备寄存器叫特殊寄存器,用来 控制和交互外围设备。CPU对内存的操作可以做到按位进行,而特殊寄存器是4字节对齐并占四个字节。你可以象unsigned int变量一样操作特殊寄存器(有些人可能更喜欢uint32_t,认为这样体现寄存器占用4个字节的特点。uint32_t在C99 头文件中有定义)。而这里,为了体现寄存器本身作为寄存器的含义而非它的物理意义的,我们做如下定义: typedef uint32_t special_register; Evaluator-7T板子上有一个按钮(可以认为是外设之一)。按下该按钮可以对IOPDATA寄存器第8位置1,相反,释放按钮会将该位重新清0。我们使用枚举方法为IOPDATA寄存器的第8位置定义一个掩码mask: enum { button = 0x100 }; IOPDATA寄存器对应的地址为0x3FF5008,我们可以用宏形象地定义IOPDATA: #define IOPDATA (*(special_register *)0x03FF5008) 有了这个定义,我们执行下面的循环就可以使CPU一直等待该按钮被按下: while ((IOPDATA & button) == 0) ; 然而这个期望必须建立在编译器不对代码进行优化的前提假设之上。如果编译器优化这段代码,那么它会认为在这个循环中没有什么会改变 IOPDATA而且认为条件判断结果总是真或假,最终优化的结果是只对(IOPDATA & button)==0判断一次,之后的循环都不在对其进行判断,其等同于: if ((IOPDATA & button) == 0) for (;;) ; 显然,如果条件判断结果为真(那么之后都会认为是真),那么这段代码将会陷入死循环。如果判断为假,那么循环就此结束。可以看出,优化的代码效率更高,因为每次循环相比原来的执行时间要短。不幸的是,这段优化代码使得它根本就不能响应按钮的每次动作。那么,如何解决这个问题呢?解决的关键就是不要让编译器优化这段代码,使用volatile就可以办到这一点。我们修改前面关于IOPDATA的宏定义: #define IOPDATA (*(special_register volatile *)0x03FF5008) 这个定义将IOPDATA 定义为volatile类型的寄存器。volatile隐含地告诉编译器特殊寄存器可能会改变内容,即使没有任何显式地代码去改变它的内容。这样一来,编译器就不对IOPDATA作优化,而是每次都去访问IOPDATA,这其实正是我们所期望的。 2.无意中降低了效率 有时候,如果不注意的话,使用volatile会无意中降低代码效率。举个例子。Evaluator-7T有一个七段数码显示器见下图: 在IOPDATA 寄存器中第10到16位用来控制显示器的每一段。比如第10位就是用来控制顶部的那段显示,置1则点亮它,清0则熄灭它。我们可以定义一个掩码mask来覆盖从第10到16的所有位: enum { display = 0x1FC00 }; 假设变量b用来控制这7段显示器的每一段显示,并且b的值已经你想要设置值(准备用来显示哪几段和熄灭哪几段,其它无关的位均为0)。那么你想要改变设置新的显示方式的操作就是: IOPDATA = b; 但是这种赋值可能会改变第10到16位之外的其它位,这是我们不期望的。所以,采用下面的方法更好: IOPDATA |= b 但是,使用 |= 并不能熄灭那些已经点亮的显示段(1 | 0 -> 1),所以我们可以用下面的函数达到目的: void display_put(uint32_t b) { IOPDATA &= ~display; IOPDATA |= b; } 不过,可能没想到的是这样的操作在无意中降低了代码效率。因为我们定义IOPDATA为 volatile类型,它阻止了编译器对代码的优化,要求任何读写IOPDATA的操作都死死板板地进行。IOPDATA &= ~display的等价表现为IOPDATA = IOPDATA & ~display,也就是先从IOPDATA读出内容然后与上~display,最后又回写IOPDATA。同理,IOPDATA |=b也有相似的过程。整个过程分别有2次读IOPDATA和2次写IOPDATA的操作。如果IOPDATA不使用volatile,那么编译器会要求将IOPDATA & ~display的结果放在CPU寄存器中,直到完成IOPDATA |= b操作才写回特殊寄存器IOPDATA。显然后者较之前者分别省掉了1次读IOPDATA和1次I写OPDATA的耗时操作(外设操作是最耗时的),效率要高很多。如果你想使用volatile但又能使能优化功能,你可以将函数作如下的修改: void display_put(uint32_t b) { register uint32_t temp = IOPDATA; temp &= ~display; temp |= b; IOPDATA = temp; } 这样做有点烦琐,下面的等效方法更简单: void display_put(uint32_t b) { IOPDATA = (IOPDATA & ~display) | b; } 结论:从该例子看出,它并不鼓励使用volatile,即使要用也要很小心,因为volatile可能在无意中降低了代码效率,而你却无法察觉。但是,我们说,不鼓励并不是说就不能或不要用,而是要懂得何时用,怎么用好它。其所谓智用了。

    时间:2014-03-23 关键词: 嵌入式 嵌入式开发 c volatile

  • C语言中auto register static const volatile 区别

    1)auto 这个关键字用于声明变量的生存期为自动,即将不在任何类、结构、枚举、联合和函数中定义的变量视为全局变量,而在函数中定义的变量视为局部变量。这个关键字不怎么多写,因为所有的变量默认就是auto的。 (2)register 这个关键字命令编译器尽可能的将变量存在CPU内部寄存器中而不是通过内存寻址访问以提高效率。 (3)static 常见的两种用途: 1>统计函数被调用的次数; 2>减少局部数组建立和赋值的开销.变量的建立和赋值是需要一定的处理器开销的,特别是数组等含有较多元素的存储类型。在一些含有较多的变量并且被经常调用的函数中,可以将一些数组声明为static类型,以减少建立或者初始化这些变量的开销. 详细说明: 1>、变量会被放在程序的全局存储区中,这样可以在下一次调用的时候还可以保持原来的赋值。这一点是它与栈变量和堆变量的区别。 2>、变量用static告知编译器,自己仅仅在变量的作用范围内可见。这一点是它与全局变量的区别。 3>当static用来修饰全局变量时,它就改变了全局变量的作用域,使其不能被别的程序extern,限制在了当前文件里,但是没有改变其存放位置,还是在全局静态储存区。 使用注意: 1>若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度; 2>若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度; 3>设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题(只要输入数据相同就应产生相同的输出)。 (4)const 被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。它可以修饰函数的参数、返回值,甚至函数的定义体。 作用: 1>修饰输入参数 a.对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const引用传递”,目的是提高效率。例如将void Func(A a) 改为void Func(const A &a)。 b.对于内部数据类型的输入参数,不要将“值传递”的方式改为“const引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如void Func(int x) 不应该改为void Func(const int &x)。 2>用const修饰函数的返回值 a.如果给以“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const修饰的同类型指针。 如对于: const char * GetString(void); 如下语句将出现编译错误: char *str = GetString();//cannot convert from 'const char *' to 'char *'; 正确的用法是: const char *str = GetString(); b.如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const修饰没有任何价值。如不要把函数int GetInt(void) 写成const int GetInt(void)。 3>const成员函数的声明中,const关键字只能放在函数声明的尾部,表示该类成员不修改对象. 说明: const type m; //修饰m为不可改变 示例: typedef char * pStr; //新的类型pStr; char string[4] = "abc"; const char *p1 = string; p1++; //正确,上边修饰的是*p1,p1可变 const pStr p2 = string; p2++; //错误,上边修饰的是p2,p2不可变,*p2可变 同理,const修饰指针时用此原则判断就不会混淆了。 const int *value; //*value不可变,value可变 int* const value; //value不可变,*value可变 const (int *) value; //(int *)是一种type,value不可变,*value可变 //逻辑上这样理解,编译不能通过,需要tydef int* NewType; const int* const value;//*value,value都不可变 (5)volatile 表明某个变量的值可能在外部被改变,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。它可以适用于基础类型如:int,char,long......也适用于C的结构和C++的类。当对结构或者类对象使用volatile修饰的时候,结构或者类的所有成员都会被视为volatile. 该关键字在多线程环境下经常使用,因为在编写多线程的程序时,同一个变量可能被多个线程修改,而程序通过该变量同步各个线程。 简单示例: DWORD __stdcall threadFunc(LPVOID signal) { int* intSignal=reinterpret_cast(signal); *intSignal=2; while(*intSignal!=1) sleep(1000); return 0; } 该线程启动时将intSignal 置为2,然后循环等待直到intSignal 为1 时退出。显然intSignal的值必须在外部被改变,否则该线程不会退出。但是实际运行的时候该线程却不会退出,即使在外部将它的值改为1,看一下对应的伪汇编代码就明白了: mov ax,signal label: if(ax!=1) goto label 对于C编译器来说,它并不知道这个值会被其他线程修改。自然就把它cache在寄存器里面。C 编译器是没有线程概念的,这时候就需要用到volatile。volatile 的本意是指:这个值可能会在当前线程外部被改变。也就是说,我们要在threadFunc中的intSignal前面加上volatile关键字,这时候,编译器知道该变量的值会在外部改变,因此每次访问该变量时会重新读取,所作的循环变为如下面伪码所示: label: mov ax,signal if(ax!=1) goto label 注意:一个参数既可以是const同时是volatile,是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。 (6)extern extern 意为“外来的”···它的作用在于告诉编译器:有这个变量,它可能不存在当前的文件中,但它肯定要存在于工程中的某一个源文件中或者一个Dll的输出中。 扩展阅读:C语言可变参数函数使用总结

    时间:2013-05-13 关键词: register static const 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因为程序不应该试图去修改它。      2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。      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;       }  volatile的本意是“易变的” 由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如:  static int i=0;  int main(void)  {  ...  while (1)  {  if (i) dosomething();  }  }  /* Interrupt service routine. */  void ISR_2(void)  {  i=1;  }  程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过i,因此 可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被调用。如果将将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。  一般说来,volatile用在如下的几个地方:  1、中断服务程序中修改的供其它程序检测的变量需要加volatile;  2、多任务环境下各任务间共享的标志应该加volatile;  3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;  另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实  现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。  volatile的本意是“易变的”   由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如:  static int i=0;  int main(void)  {  ...  while (1)  {  if (i) dosomething();  }  }  /* Interrupt service routine. */  void ISR_2(void)  {  i=1;  }  程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过i,因此  可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被  调用。如果将将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。  一般说来,volatile用在如下的几个地方:  1、中断服务程序中修改的供其它程序检测的变量需要加volatile;  2、多任务环境下各任务间共享的标志应该加volatile;  3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;  另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实  现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。  关键在于两个地方:          1. 编译器的优化  (请高手帮我看看下面的理解)  在本次线程内, 当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中;以后,再取变量值时,就直接从寄存器中取值;  当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以便保持一致  当变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致  当该寄存器在因别的线程等而改变了值,原变量的值不会改变,从而造成应用程序读取的值和实际的变量值不一致       举一个不太准确的例子:   发薪资时,会计每次都把员工叫来登记他们的银行卡号;一次会计为了省事,没有即时登记,用了以前登记的银行卡号;刚好一个员工的银行卡丢了,已挂失该银行卡号;从而造成该员工领不到工资   员工 -- 原始变量地址   银行卡号 -- 原始变量在寄存器的备份   2. 在什么情况下会出现(如1楼所说)      1). 并行设备的硬件寄存器(如:状态寄存器)       2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)       3). 多线程应用中被几个任务共享的变量         补充: volatile应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人;   “易变”是因为外在因素引起的,象多线程,中断等,并不是因为用volatile修饰了的变量就是“易变”了,假如没有外因,即使用volatile定义,它也不会变化;  而用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可以保证对特殊地址的稳定访问。  >>>>注意,在vc6中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过插入汇编代码,测试有无volatile关键字,对程序最终代码的影响:  >>>>首先,用classwizard建一个win32 console工程,插入一个voltest.cpp文件,输入下面的代码:  >>  #include <stdio.h>  void main()  {  int i=10;  int a = i;  printf("i= %d",a);  //下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道  __asm {  mov dword ptr [ebp-4], 20h  }  int b = i;  printf("i= %d",b);  }        然后,在调试版本模式运行程序,输出结果如下:  i = 10  i = 32  然后,在release版本模式运行程序,输出结果如下:  i = 10  i = 10  输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。下面,我们把 i的声明加上volatile关键字,看看有什么变化:  #include <stdio.h>  void main()  {  volatile int i=10;  int a = i;  printf("i= %d",a);  __asm {  mov dword ptr [ebp-4], 20h  }  int b = i;  printf("i= %d",b);  }        分别在调试版本和release版本运行程序,输出都是:  i = 10  i = 32  这说明这个关键字发挥了它的作用!  volatile对应的变量可能在你的程序本身不知道的情况下发生改变  比如多线程的程序,共同访问的内存当中,多个程序都可以操纵这个变量  你自己的程序,是无法判定合适这个变量会发生变化  还比如,他和一个外部设备的某个状态对应,当外部设备发生操作的时候,通过驱动程序和中断事件,系统改变了这个变量的数值,而你的程序并不知道。  对于volatile类型的变量,系统每次用到他的时候都是直接从对应的内存当中提取,而不会利用cache当中的原有数值,以适应它的未知何时会发生的变化,系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。  典型的例子  for ( int i=0; i<100000; i++);  这个语句用来测试空循环的速度的  但是编译器肯定要把它优化掉,根本就不执行  如果你写成   for ( volatile int i=0; i<100000; i++);  它就会执行了

    时间:2012-05-12 关键词: 定义 单片机 volatile

  • C语言中的volatile关键字

    volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改。   用volatile关键字声明的变量i每一次被访问时,执行部件都会从i相应的内存单元中取出i的值。   没有用volatile关键字声明的变量i在被访问的时候可能直接从cpu的寄存器中取值(因为之前i被访问过,也就是说之前就从内存中取出i的值保存到某个寄存器中),之所以直接从寄存器中取值,而不去内存中取值,是因为编译器优化代码的结果(访问cpu寄存器比访问ram快的多)。   以上两种情况的区别在于被编译成汇编代码之后,两者是不一样的。之所以这样做是因为变量i可能会经常变化,保证对特殊地址的稳定访问。   volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改   ,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的   代码就不再进行优化,从而可以提供对特殊地址的稳定访问。   volatile变量可能用于如下几种情况:   (1) 并行设备的硬件寄存器(如:状态寄存器,例中的代码属于此类);   (2) 一个中断服务子程序中会访问到的非自动变量(也就是全局变量);   (3) 多线程应用中被几个任务共享的变量。   使用该关键字的例子如下: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可以保证对特殊地址的稳定访问。   注意,在vc6中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过插入汇编代码,测试有无volatile关键字,对程序最终代码的影响:   首先,用classwizard建一个win32 console工程,插入一个voltest.cpp文件,输入下面的   代码:   #i nclude <stdio.h>   void main()   {   int i=10;   int a = i;   printf("i= %dn",a);   //下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道   __asm {   mov dword ptr [ebp-4], 20h   }   int b = i;   printf("i= %dn",b);   }   然后,在调试版本模式运行程序,输出结果如下:   i = 10   i = 32   然后,在release版本模式运行程序,输出结果如下:[!--empirenews.page--]   i = 10   i = 10   输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。   下面,我们把 i的声明加上volatile关键字,看看有什么变化:   #i nclude <stdio.h>   void main()   {   volatile int i=10;   int a = i;   printf("i= %dn",a);   __asm {   mov dword ptr [ebp-4], 20h   }   int b = i;   printf("i= %dn",b);   }   分别在调试版本和release版本运行程序,输出都是:   i = 10   i = 32   这说明这个关键字发挥了它的作用!

    时间:2011-05-27 关键词: C语言 关键字 言中的 设计教程 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因为程序不应该试图去修改它。      2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。      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;       }  volatile的本意是“易变的” 由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如:  static int i=0;  int main(void)  {  ...  while (1)  {  if (i) dosomething();  }  }  /* Interrupt service routine. */  void ISR_2(void)  {  i=1;  }  程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过i,因此 可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被调用。如果将将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。  一般说来,volatile用在如下的几个地方:  1、中断服务程序中修改的供其它程序检测的变量需要加volatile;  2、多任务环境下各任务间共享的标志应该加volatile;  3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;  另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实  现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。  volatile的本意是“易变的”   由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如:  static int i=0;  int main(void)  {  ...  while (1)  {  if (i) dosomething();  }  }  /* Interrupt service routine. */  void ISR_2(void)  {  i=1;  }  程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过i,因此  可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被  调用。如果将将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。  一般说来,volatile用在如下的几个地方:  1、中断服务程序中修改的供其它程序检测的变量需要加volatile;  2、多任务环境下各任务间共享的标志应该加volatile;  3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;  另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实  现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。  关键在于两个地方:          1. 编译器的优化  (请高手帮我看看下面的理解)  在本次线程内, 当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中;以后,再取变量值时,就直接从寄存器中取值;  当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以便保持一致  当变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致  当该寄存器在因别的线程等而改变了值,原变量的值不会改变,从而造成应用程序读取的值和实际的变量值不一致   举一个不太准确的例子:   发薪资时,会计每次都把员工叫来登记他们的银行卡号;一次会计为了省事,没有即时登记,用了以前登记的银行卡号;刚好一个员工的银行卡丢了,已挂失该银行卡号;从而造成该员工领不到工资   员工 -- 原始变量地址   银行卡号 -- 原始变量在寄存器的备份   2. 在什么情况下会出现(如1楼所说)      1). 并行设备的硬件寄存器(如:状态寄存器)       2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)       3). 多线程应用中被几个任务共享的变量         补充: volatile应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人;   “易变”是因为外在因素引起的,象多线程,中断等,并不是因为用volatile修饰了的变量就是“易变”了,假如没有外因,即使用volatile定义,它也不会变化;  而用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可以保证对特殊地址的稳定访问。  >>>>注意,在vc6中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过插入汇编代码,测试有无volatile关键字,对程序最终代码的影响:  >>>>首先,用classwizard建一个win32 console工程,插入一个voltest.cpp文件,输入下面的代码:  >>  #include <stdio.h>  void main()  {  int i=10;  int a = i;  printf("i= %d",a);  //下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道  __asm {  mov dword ptr [ebp-4], 20h  }  int b = i;  printf("i= %d",b);  }        然后,在调试版本模式运行程序,输出结果如下:  i = 10  i = 32  然后,在release版本模式运行程序,输出结果如下:  i = 10  i = 10  输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。下面,我们把 i的声明加上volatile关键字,看看有什么变化:  #include <stdio.h>  void main()  {  volatile int i=10;  int a = i;  printf("i= %d",a);  __asm {  mov dword ptr [ebp-4], 20h  }  int b = i;  printf("i= %d",b);  }        分别在调试版本和release版本运行程序,输出都是:  i = 10  i = 32  这说明这个关键字发挥了它的作用!  volatile对应的变量可能在你的程序本身不知道的情况下发生改变  比如多线程的程序,共同访问的内存当中,多个程序都可以操纵这个变量  你自己的程序,是无法判定合适这个变量会发生变化  还比如,他和一个外部设备的某个状态对应,当外部设备发生操作的时候,通过驱动程序和中断事件,系统改变了这个变量的数值,而你的程序并不知道。  对于volatile类型的变量,系统每次用到他的时候都是直接从对应的内存当中提取,而不会利用cache当中的原有数值,以适应它的未知何时会发生的变化,系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。  典型的例子  for ( int i=0; i<100000; i++);  这个语句用来测试空循环的速度的  但是编译器肯定要把它优化掉,根本就不执行  如果你写成   for ( volatile int i=0; i<100000; i++);  它就会执行了  

    时间:2011-03-27 关键词: 定义 详解 单片机 volatile

发布文章

技术子站