为什么波动存在?
volatile
关键字有什么作用? 在C ++中,它解决了什么问题?
就我而言,我从来不知道需要它。
如果你正在从内存中读取一个完全独立的进程/设备/可能写入的内容,那么就需要volatile
。
我曾经在直线C的多处理器系统中使用双端口ram。我们使用硬件pipe理的16位值作为信号量来知道另一个人何时完成。 基本上我们做到了这一点:
void waitForSemaphore() { volatile uint16_t* semPtr = WELL_KNOWN_SEM_ADDR;/*well known address to my semaphore*/ while ((*semPtr) != IS_OK_FOR_ME_TO_PROCEED); }
没有不volatile
,优化器将这个循环看作是无用的(这个人从来没有设置这个值!他是疯了,摆脱了那个代码!),我的代码将继续进行而没有获得信号量,以后会引发问题。
在开发embedded式系统或设备驱动程序时需要volatile
,因此您需要读取或写入内存映射的硬件设备。 特定设备寄存器的内容可能会随时更改,因此您需要使用volatile
关键字来确保编译器不会优化这些访问。
大多数现代处理器都具有64位以上精度的浮点寄存器。 这样,如果您对双精度数字执行多个操作,则实际上得到的精度要高于将每个中间结果截断为64位的结果。
这通常很好,但这意味着,根据编译器分配的寄存器和优化的方式,对于完全相同的input,完全相同的操作会产生不同的结果。 如果您需要一致性,那么您可以使用volatile关键字强制每个操作回到内存。
对于一些没有代数意义但是减less浮点错误的algorithm,如Kahan求和,也是有用的。 在代数上它是一个nop,所以经常会得到不正确的优化,除非有一些中间variables是不稳定的。
从embedded式系统文章Dan Saks:
“一个volatile对象是一个值可能会自发改变的对象,也就是说,当你声明一个对象是volatile时,你告诉编译器该对象可能会改变状态,即使程序中没有任何语句会改变它。
Saks先生关于volatile关键字的两篇精彩文章的链接:
http://www.embedded.com/columns/programmingpointers/174300478 http://www.embedded.com/columns/programmingpointers/175801310
在实现无锁数据结构时,你必须使用volatile。 否则,编译器可以自由地优化对variables的访问,这将改变语义。
换句话说,volatile指示编译器访问此variables必须对应于物理内存读/写操作。
例如,这是如何在Win32 API中声明InterlockedIncrement:
LONG __cdecl InterlockedIncrement( __inout LONG volatile *Addend );
在标准C中,使用volatile
的地方之一是使用信号处理程序。 事实上,在标准C中,您可以安全地在信号处理程序中执行的操作是修改volatile sig_atomic_t
variables,或者快速退出。 实际上,AFAIK是标准C中唯一需要使用volatile
来避免未定义行为的地方。
ISO / IEC 9899:2011§7.14.1.1
signal
function¶5如果信号不是由于调用
abort
或raise
函数而发生的,则如果信号处理程序引用静态或线程存储持续时间不是非lockingprimefaces对象的任何对象,而是通过分配对声明为volatile sig_atomic_t
的对象的值,或者信号处理程序调用标准库中除abort
函数,_Exit
函数,quick_exit
函数或第一个参数的signal
函数以外的quick_exit
函数,引起调用处理程序的信号。 而且,如果对signal
函数的这种调用导致SIG_ERR返回,则errno
的值是不确定的。 252)如果任何信号由asynchronous信号处理程序生成,则行为是不确定的。
这意味着在标准C中,你可以写:
static volatile sig_atomic_t sig_num = 0; static void sig_handler(int signum) { signal(signum, sig_handler); sig_num = signum; }
而不是其他的。
POSIX在信号处理程序中可以做的事情要宽松得多,但仍然有一些限制(其中一个限制是标准I / O库 – printf()
等printf()
不能安全地使用。
为embedded式开发,我有一个循环,检查可以在中断处理程序中更改的variables。 如果没有“volatile”,循环就成为noop – 就编译器而言,variables永远不会改变,所以它优化了检查。
同样的事情也适用于一个variables,在一个更传统的环境中,这个variables可能在不同的线程中被改变,但是我们经常做同步调用,所以编译器没有那么自由的优化。
- 你必须使用它来实现自旋锁以及一些(所有?)无锁的数据结构
- 在primefaces操作/指令中使用它
- 曾帮助我克服编译器的bug(优化期间错误生成的代码)
我在20世纪90年代早期曾经使用过的一个大型应用程序包含了使用setjmp和longjmp的基于C的exception处理。 volatile关键字对于需要在作为“catch”子句的代码块中保存的variables是必要的,以免这些variables被存储在寄存器中并被longjmp清除。
除了按照预期使用它外,(模板)元编程中还使用volatile。 它可以用来防止意外的重载,因为volatile属性(如const)参与重载parsing。
当编译器坚持优化一个我希望能够看到的variables时,我已经在debugging版本中使用它。
volatile
关键字旨在防止编译器对可能以编译器无法确定的方式更改的对象应用任何优化。
声明为volatile
对象从优化中省略,因为它们的值可以随时通过当前代码范围之外的代码进行更改。 即使先前的指令要求来自同一对象的值,系统总是从内存位置读取volatile
对象的当前值,而不是将其值保存在临时寄存器中。
考虑以下情况
1)范围外的中断服务程序修改的全局variables。
2)multithreading应用程序中的全局variables。
如果我们不使用volatile限定符,可能会出现以下问题
1)开启优化时,代码可能无法正常工作。
2)中断启用和使用时,代码可能无法正常工作。
易变:程序员最好的朋友
https://en.wikipedia.org/wiki/Volatile_(computer_programming);
除了使用volatile关键字来告诉编译器不要优化对某个variables(可以被线程或中断例程修改)的访问权限之外,它还可以用来删除一些编译器错误 – 是的,它可以是 —。
比如我在embedded式平台上工作,编译器就variables的值做了一些错误的假设。 如果代码没有优化,程序将运行正常。 随着优化(这是真正需要的,因为这是一个关键的例程)代码将无法正常工作。 唯一的解决scheme(虽然不是很正确)是将“错误”variables声明为volatile。
我应该提醒你的一个用途是,在信号处理函数中,如果你想访问/修改一个全局variables(例如,标记为exit = true),你必须声明该variables为“volatile”。
你的程序似乎工作,即使没有volatile
关键字? 也许这是原因:
正如前面提到的那样, volatile
关键字有助于像
volatile int* p = ...; // point to some memory while( *p!=0 ) {} // loop until the memory becomes zero
但是,一旦外部或非内联函数被调用,似乎几乎没有效果。 例如:
while( *p!=0 ) { g(); }
然后有或者没有volatile
产生几乎相同的结果。
只要g()可以完全内联,编译器就可以看到正在发生的一切,因此可以优化。 但是当程序调用一个编译器无法看到的地方时,编译器不再做任何假设。 因此,编译器将生成总是直接从内存中读取的代码。
但是当心,当函数g()变为内联(由于显式变化或者由于编译器/链接器的巧妙),那么如果你忘记volatile
关键字,你的代码可能会中断!
因此,我build议添加volatile
关键字,即使您的程序似乎没有工作。 它使未来变化的意图更清晰和更强大。