什么是volatile
关键词呢?在C++中它解决了什么问题?
就我而言,我从来没有故意需要它.
volatile
如果你从内存中的一个位置读取,比如一个完全独立的进程/设备/可能写入的内容,则需要它.
我曾经在直接C的多处理器系统中使用双端口ram.我们使用硬件管理的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
,优化器认为循环是无用的(那个人永远不会设置值!他很疯狂,摆脱那些代码!)并且我的代码将在没有获得信号量的情况下继续进行,导致以后出现问题.
volatile
在开发嵌入式系统或设备驱动程序时需要读取或写入内存映射硬件设备.特定设备寄存器的内容可能随时更改,因此您需要使用volatile
关键字来确保编译器不会优化此类访问.
大多数现代处理器都具有超过64位精度的浮点寄存器.这样,如果对双精度数运行多个操作,实际上得到的答案要比将每个中间结果截断为64位时更高.
这通常很好,但这意味着根据编译器分配寄存器的方式和优化,您将在完全相同的输入上对完全相同的操作产生不同的结果.如果需要一致性,则可以使用volatile关键字强制每个操作返回内存.
它对于一些没有代数意义但减少浮点误差的算法也很有用,例如Kahan求和.代数上它是一个nop,因此除非某些中间变量是易失性的,否则它往往会被错误地优化出来.
来自Dan Saks的嵌入式系统文章:
"volatile对象的值可能会自发地发生变化.也就是说,当你声明一个对象是volatile时,你告诉编译器该对象可能会改变状态,即使程序中的任何语句都没有改变它."
链接到Saks先生关于volatile关键字的2篇精彩文章:
http://www.embedded.com/columns/programmingpointers/174300478 http://www.embedded.com/columns/programmingpointers/175801310
在实现无锁数据结构时,您必须使用volatile.否则,编译器可以自由地优化对变量的访问,这将改变语义.
换句话说,volatile告诉编译器访问此变量必须对应于物理内存读/写操作.
例如,这是在Win32 API中声明InterlockedIncrement的方式:
LONG __cdecl InterlockedIncrement( __inout LONG volatile *Addend );
我在20世纪90年代早期使用的大型应用程序包含使用setjmp和longjmp的基于C的异常处理.volatile的关键字对于需要在作为"catch"子句的代码块中保留的变量是必要的,以免这些变量存储在寄存器中并被longjmp消灭.
在标准C中,使用的一个地方volatile
是信号处理程序.事实上,在标准C中,您可以安全地在信号处理程序中执行的操作是修改volatile sig_atomic_t
变量,或快速退出.实际上,AFAIK,它是标准C中唯一volatile
需要使用以避免未定义行为的地方.
ISO/IEC 9899:2011§7.14.1.1
signal
功能5如果信号的出现不是调用
abort
orraise
函数的结果,那么如果信号处理程序引用具有静态或线程存储持续时间但不是无锁原子对象的任何对象,则行为是未定义的,除非通过赋值到声明为的对象volatile sig_atomic_t
,或者信号处理程序调用标准库中除abort
函数,_Exit
函数,quick_exit
函数或signal
函数之外的任何函数,第一个参数等于对应于导致调用的信号的信号编号处理程序.此外,如果对signal
函数的这种调用导致SIG_ERR返回,则值errno
是不确定的.252)252)如果异步信号处理程序生成任何信号,则行为未定义.
这意味着在标准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()
等 - 不能安全使用).
为嵌入式开发,我有一个循环,它检查可以在中断处理程序中更改的变量.如果没有"volatile",循环就会变成noop - 就编译器而言,变量永远不会改变,因此它会优化检查.
同样的事情适用于在更传统的环境中可能在不同线程中更改的变量,但是我们经常进行同步调用,因此编译器不是那么自由的优化.
我已经在调试版本中使用它,当编译器坚持优化我希望能够在逐步执行代码时看到的变量.
除了按预期使用它之外,在(模板)元编程中使用volatile.它可用于防止意外过载,因为volatile属性(如const)参与重载决策.
templateclass Foo { std::enable_if_t f(T& t) { std::cout << 1 << t; } void f(T volatile& t) { std::cout << 2 << const_cast (t); } void bar() { T t; f(t); } };
这是合法的; 两个重载都可以调用,并且几乎完全相同.volatile
超载中的演员是合法的,因为我们知道bar T
无论如何都不会通过非易失性.该volatile
版本是严格糟糕,虽然如此,从来没有过载的分辨率选择,如果非易失性f
是可用的.
请注意,代码实际上从不依赖于volatile
内存访问.
你必须使用它来实现自旋锁以及一些(所有?)无锁数据结构
将它与原子操作/指令一起使用
帮助我克服了编译器的错误(在优化过程中错误生成的代码)