8

考虑以下:

volatile uint32_t i;

我如何知道 gcc 是否将 i 视为 volatile?它将被声明为这样,因为附近没有代码会修改它,并且修改它很可能是由于一些中断。

我不是世界上最差的汇编程序员,但我在电视上玩过一个。有人可以帮我理解它会有什么不同吗?

如果您采用以下愚蠢的代码:

#include <stdio.h>
#include <inttypes.h>

volatile uint32_t i;

int main(void)
{
        if (i == 64738)
                return 0;
        else
                return 1;
}

编译成object格式,通过objdump反汇编,去掉'volatile'后也一样,没有区别(根据diff)。volatile 声明是否太接近其检查或修改的位置,还是在声明 volatile 时我应该总是使用某种原子类型?一些优化标志会影响这一点吗?

请注意,我的愚蠢样本与我的问题不完全匹配,我意识到这一点。我只是想知道 gcc 是否将变量视为 volatile,因此我正在研究小型转储以尝试找出差异。

4

7 回答 7

14

在某些情况下,许多编译器不会按应有的方式处理 volatile。如果您经常处理 volatiles 以避免令人讨厌的意外,请参阅这篇论文:Volatiles 被错误编译,以及如何处理它。它还包含以标准引用为后盾的对 volatile 的很好描述。

100% 确定,对于这样一个简单的示例,请查看汇编输出。

于 2009-03-13T13:05:09.140 回答
12

尝试在循环外设置变量并在循环内读取它。在非易失性情况下,编译器可能(或可能不会)将其放入寄存器或使其成为编译时间常数或循环之前的其他内容,因为它“知道”它不会改变,而如果它是易失性的,它会每次通过循环从变量空间中读取它。

基本上,当您将某些内容声明为 volatile 时,您是在告诉编译器不要进行某些优化。如果它决定不做这些优化,你不知道它没有做这些优化是因为它被声明为 volatile,或者只是它决定它需要这些寄存器来做其他事情,或者它没有注意到它可以转向它变成一个编译时间常数。

于 2009-03-13T13:03:53.853 回答
6

据我所知, volatile 有助于优化器。例如,如果您的代码如下所示:

int foo() {
    int x = 0;
    while (x);
    return 42;
}

“while”循环将从二进制文件中优化。

但是,如果您将“x”定义为易失性(即volatile int x;),那么编译器将不理会循环。

于 2009-03-13T13:05:56.360 回答
5

你的小样本不足以显示任何东西。volatile 变量和非 volatile 变量之间的区别在于,代码中的每次加载或存储都必须在可执行文件中为 volatile 变量精确生成一次加载或存储,而编译器可以自由优化非-易失性变量。如果您的样本中有一个 i 负载,这就是我对易失性和非易失性的期望。

为了显示差异,您将不得不有多余的负载和/或存储。尝试类似的东西

int i = 5;
int j = i + 2;
i = 5;
i = 5;
printf("%d %d\n", i, j);

在非易失性和易失性之间改变 i。您可能必须启用某种程度的优化才能看到差异。

那里的代码有三个存储和两个 i 加载,可以优化为一个存储,如果 i 不是易失性的,可能会加载一个。如果 i 被声明为 volatile,则无论进行何种优化,所有存储和加载都应按顺序显示在目标代码中。如果他们不这样做,你就有一个编译器错误。

于 2009-03-13T13:50:50.870 回答
4

在错误引用或否决之前阅读标准。这是来自 n2798 的引述:

7.1.6.1 cv 限定符

7 注意: volatile 是对实现的提示,以避免涉及对象的激进优化,因为对象的值可能会通过实现无法检测的方式进行更改。详细语义见 1.9。通常,volatile 的语义在 C++ 中与在 C 中的语义相同。

关键字volatile充当提示。很像register关键字。但是,volatile要求编译器保留所有优化。这样,它不会在寄存器或缓存中保留变量的副本(以优化访问速度),而是在每次请求时从内存中获取它。

既然有这么多的混乱:更多。实际上,C99 标准确实说每次读取 volatile 限定对象时都必须对其进行查找,等等,正如其他人所指出的那样。但是,还有另一部分说构成可变访问的内容是实现定义的。因此,一个对硬件了如指掌的编译器会知道,例如,当您有一个自动 volatile 限定变量并且其地址从未被占用时,它不会被放入内存的敏感区域并且几乎肯定会忽略提示并优化它。

此关键字查找错误处理的用法setjmplongjmp类型。您唯一需要记住的是:当您认为变量可能会更改时,您提供 volatile 关键字。也就是说,您可以获取一个普通对象并通过几次强制转换来管理。

要记住的另一件事是,什么构成易失性访问的定义由标准留给实现。

如果您真的想要不同的程序集进行优化编译

于 2009-03-13T13:04:35.007 回答
4

它应该始终将其视为易失性。

代码相同的原因是 volatile 只是指示编译器在每次访问它时从内存中加载变量。即使启用了优化,编译器仍然需要在您编写的代码中从内存中加载 i 一次,因为它无法在编译时推断 i 的值。如果您反复访问它,您会发现不同。

于 2009-03-13T13:06:02.727 回答
4

任何现代编译器都有多个阶段。一个相当简单但有趣的问题是变量本身的声明是否被正确解析。这很容易,因为 C++ 名称修饰应该根据易失性而有所不同。因此,如果你编译两次,一次定义了 volatile,符号表应该略有不同。

于 2009-03-13T13:35:37.833 回答