我是嵌入式开发人员,在使用I / O端口时使用volatile关键字。但我的项目经理建议使用volatile关键字是有害的并且有很多缺点,但我发现在大多数情况下,volatile在嵌入式编程中很有用,据我所知,volatile在内核代码中是有害的,因为我们代码的更改将变为不可预知的。嵌入式系统中使用volatile还有什么缺点吗?
答案 0 :(得分:23)
不,volatile
无害。在任何情况下。永远。没有可能的格式良好的代码片段会随着volatile
添加到对象(以及指向该对象的指针)而中断。但是, volatile
通常知之甚少。内核文档声明volatile
被认为有害的原因是人们继续使用它来破坏内核线程之间的同步。特别是,他们使用了volatile
个整数变量,就好像对它们的访问保证是原子的,它不是。
volatile
也没用,特别是如果你去裸机,你将需要它。但是,与任何其他工具一样,在使用它之前了解volatile
的语义非常重要。
volatile
是什么标准中对volatile
个对象的访问权限被视为副作用,其方式与++
和--
的递增或递减相同。特别是,这意味着5.1.2.3(3),即
(...)实际实现不需要评估表达式的一部分,如果它可以推断出它的值没有被使用并且没有产生所需的副作用(包括由调用函数或访问volatile对象引起的任何副作用)
不适用。编译器必须在每个序列点丢弃它认为知道的volatile
变量值的所有内容。 (与其他副作用一样,当volatile
个对象的访问发生时,由序列点控制)
这种影响主要是禁止某些优化。举个例子,代码
int i;
void foo(void) {
i = 0;
while(i == 0) {
// do stuff that does not touch i
}
}
允许编译器使其成为一个永远不会再次检查i
的无限循环,因为它可以推断出i
的值在循环中没有改变,因此i == 0
永远不会是假的。 即使存在可以改变i
的其他线程或中断处理程序,也是如此。编译器不知道它们,它并不关心。明确允许不在乎。
将此与
对比int volatile i;
void foo(void) {
i = 0;
while(i == 0) { // Note: This is still broken, only a little less so.
// do stuff that does not touch i
}
}
现在,编译器必须假设i
可以随时更改,并且无法进行此优化。当然,这意味着如果处理中断处理程序和线程,volatile
对象是同步所必需的。 然而,它们还不够。
volatile
不是 volatile
不保证原子访问权限。如果您习惯于嵌入式编程,这应该具有直观意义。如果您愿意,请考虑以下8位AVR MCU的代码:
uint32_t volatile i;
ISR(TIMER0_OVF_vect) {
++i;
}
void some_function_in_the_main_loop(void) {
for(;;) {
do_something_with(i); // This is thoroughly broken.
}
}
此代码被破坏的原因是对i
的访问不是原子的 - 在8位MCU上不能是原子的。例如,在这种简单的情况下,可能会发生以下情况:
i
是0x0000ffff
do_something_with(i)
即将被称为i
的高两个字节复制到此调用的参数槽中i
。 i
的低两个字节溢出,现在是0
。 i
现在是0x00010000
。i
的低两个字节被复制到参数槽do_something_with
作为参数调用0
。PC和其他平台上也可能发生类似情况。如果有的话,更多的机会可以通过更复杂的架构打开。
所以不,使用volatile
并不坏,你(通常)必须在裸机代码中使用它。但是,当你使用它时,你必须记住它不是一根魔杖,你仍然必须确保你不会绊倒自己。在嵌入式代码中,通常采用特定于平台的方式来处理原子性问题;例如,在AVR的情况下,通常的撬棍方法是在持续时间内禁用中断,如
uint32_t x;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
x = i;
}
do_something_with(x);
... ATOMIC_BLOCK
宏之前调用cli()
(禁用中断),然后sei()
(启用中断),如果事先已启用它们。
C11是第一个明确承认存在多线程的C标准,引入了一系列新的原子类型和内存防护操作,可用于线程间同步,并且在很多情况下使用{ {1}}不必要。如果您可以使用它们,那么它可能需要一段时间才能到达所有常见的嵌入式工具链。有了它们,上面的循环可以像这样修复:
volatile
......最基本的形式。更轻松的内存顺序语义的精确语义超出了SO答案的范围,因此我将坚持使用默认的顺序一致的东西。
如果您对它感兴趣,Gil Hamilton在评论中提供了一个链接,用于解释使用C11原子的无锁堆栈实现,虽然我觉得它并不是很好写出内存顺序语义本身。然而,C11模型似乎与C ++ 11内存模型密切相关,其中存在有用的表示here。如果我找到了一个特定于C11的注释的链接,我会稍后将它放在这里。
答案 1 :(得分:4)
volatile
才有用。这种变化可能会发生
setjmp
和longjmp
在所有这些情况下,必须声明您的对象volatile
,否则您的程序将无法正常运行。 (您可能会注意到不同线程之间共享的对象不在列表中。)
在所有其他情况下,您不应该,因为您可能缺少优化机会。另一方面,限定不属于上述点的对象volatile
将不会使您的代码不正确。
答案 2 :(得分:1)
Volatile告诉编译器不要优化与volatile变量有关的任何东西。
为什么"不稳定"不应该使用类型类? - 内核文档中的最佳文章
https://www.kernel.org/doc/Documentation/volatile-considered-harmful.txt
答案 3 :(得分:1)
使用volatile
在必要且适当的情况下更有可能是有害的!对volatile
的任何感知问题的解决方案不是禁止使用它,因为在许多情况下,安全和正确的语义是必要的。相反,解决方案是了解其目的和行为。
对于可能在编译器知识之外进行更改的任何数据(例如I / O和双端口或DMA存储器),这一点至关重要。访问执行上下文(如线程和中断处理程序)之间共享的内存也是必要的。这也许是困惑所在;它确保显式读取此类内存,并且不强制执行原子性或互斥 - 需要其他机制,但这并不排除volatile
,但它只是共享内存访问解决方案的一部分。 / p>
请参阅以下使用volatile的文章(并将它们发送给您的项目经理!):
答案 4 :(得分:0)
volatile是c中的一个关键字,它告诉编译器不要对该变量进行任何类型的优化。
让我举个简单的例子:
int temp;
for ( i=0 ;i <5 ; i++ )
{
temp = 5;
}
编译器将如何优化代码:
int temp;
temp = 5; /* assigned temp variable before the loop. */
for ( i=0 ;i <5 ; i++ )
{
}
但是如果我们提到volatile关键字,那么编译器将不会对temp变量进行任何类型的优化。
volatile int temp;
for ( i=0 ;i <5 ; i++ )
{
temp = 5;
}
&#34;挥发性被认为是有害的&#34; ---&GT;我不认为挥发性有害。如果您不想从编译器端进行任何优化,请使用volatile。
例如,考虑温度计公司使用这段代码,temp是一个变量,用于获取可随时改变的大气温度。因此,如果我们不使用volatile,那么编译器将进行优化,并且大气温度将始终相同。