请考虑以下事项:
volatile uint32_t i;
我如何知道gcc是否曾将我视为易变?它会被声明为因为没有附近的代码会修改它,并且修改它可能是由于某些中断。
我不是世界上最差的汇编程序员,但我在电视上播放一个。有人可以帮我理解它会有什么不同吗?
如果您采用以下愚蠢的代码:
#include <stdio.h>
#include <inttypes.h>
volatile uint32_t i;
int main(void)
{
if (i == 64738)
return 0;
else
return 1;
}
将其编译为对象格式并通过objdump反汇编,然后在删除'volatile'后执行相同操作,没有区别(根据diff)。 volatile声明是否太接近于其检查或修改的位置,或者我在声明易失性时应该总是使用某种原子类型?一些优化标志会影响这个吗?
注意,我的愚蠢样本并不完全符合我的问题,我意识到这一点。我只是试图找出gcc是否确实将变量视为易失性,所以我正在研究小型转储以寻找差异。
答案 0 :(得分:14)
在某些情况下,许多编译器不会按照应有的方式处理volatile。如果您处理挥发物以避免令人讨厌的意外,请参阅本文:Volatiles are Miscompiled, and What to Do about It。它还包含对标准引用支持的volatile的非常好的描述。
要100%确定,对于这样一个简单的例子,请检查装配输出。
答案 1 :(得分:12)
尝试在循环外设置变量并在循环内读取它。在非易失性情况下,编译器可能(或可能不)将其推入寄存器或使其成为编译时常量或循环之前的某些东西,因为它“知道”它不会改变,而如果它是不稳定的,它将会每次通过循环从变量空间中读取它。
基本上,当你声明某些东西是volatile时,你告诉编译器不要进行某些优化。如果它决定不进行那些优化,你就不会知道它没有这样做,因为它被声明为volatile,或者只是因为它决定了它需要那些寄存器,或者它没有注意它可以把它变成编译时常量。
答案 2 :(得分:6)
据我所知,volatile可以帮助优化器。例如,如果您的代码如下所示:
int foo() {
int x = 0;
while (x);
return 42;
}
“while”循环将从二进制文件中优化。
但是如果你将'x'定义为volatile(即volatile int x;
),那么编译器将保留循环。
答案 3 :(得分:5)
你的小样本不足以显示任何内容。 volatile变量和不变量之间的区别在于代码中的每个加载或存储都必须在可执行文件中为volatile变量生成一个加载或存储,而编译器可以自由地优化非加载或存储的非变量 - 易变量。如果你的样品中有一个i加载,那就是我对挥发性和非挥发性的预期。
为了显示差异,您将不得不拥有冗余负载和/或存储。尝试像
这样的东西int i = 5;
int j = i + 2;
i = 5;
i = 5;
printf("%d %d\n", i, j);
在非易失性和易失性之间改变i。您可能必须启用某种级别的优化才能看到差异。
那里的代码有三个存储和两个i的加载,可以优化到一个商店,如果我不易变,可能会加载一个。如果i被声明为volatile,则无论优化是什么,所有存储和加载都应按顺序显示在目标代码中。如果他们不这样做,你就会遇到编译错误。
答案 4 :(得分:4)
在错误引用或downvote之前阅读标准。以下是n2798的引用:
7.1.6.1 cv-qualifiers
7注意:volatile是对实现的暗示,以避免涉及对象的激进优化,因为对象的值可能会被实现无法检测到的方式更改。有关详细语义,请参见1.9。通常,volatile的语义在C ++中与在C中的相同。
关键字volatile
充当提示。与register
关键字非常相似。但是,volatile
要求编译器保留所有优化。这样,它不会在变量寄存器或缓存中保留变量的副本(以优化访问速度),而是每次请求时从内存中获取变量。
由于存在很多混乱:还有一些。事实上,C99标准确实说每次读取时都必须查找一个易变的合格对象,等等,正如其他人所指出的那样。但是,还有另一部分说明构成易失性访问的是实现定义。因此,一个知道内部硬件的编译器会知道,例如,当你有一个自动的volatile限定变量并且它的地址永远不会被占用时,它将不会放在敏感的内存区域中并且几乎肯定会被忽略提示并优化它。
此关键字可在setjmp
和longjmp
类型的错误处理中查找用法。您唯一需要记住的是:当您认为变量可能发生变化时,您会提供volatile关键字。也就是说,您可以使用普通对象并使用一些演员来管理。
要记住的另一件事是定义挥发性访问的定义是标准的实现。
如果确实想要使用优化进行不同的汇编编译
答案 5 :(得分:4)
总是将其视为易失性。
代码相同的原因是volatile只是指示编译器每次访问时都从内存加载变量。即使进行了优化,编译器仍然需要在您编写的代码中从内存中加载i一次,因为它无法在编译时推断出i的值。如果您反复访问它,您会发现不同之处。
答案 6 :(得分:4)
任何现代编译器都有多个阶段。一个相当容易但有趣的问题是变量本身的声明是否被正确解析。这很容易,因为C ++名称修改应该根据volatile的不同而不同。因此,如果您编译两次,一次使用volatile定义,则符号表应略有不同。