【问题标题】:Assign volatile to non-volatile sematics and the C standard将易失性分配给非易失性语义学和 C 标准
【发布时间】:2019-11-11 10:52:22
【问题描述】:
volatile int vfoo = 0;
void func()
{
    int bar;
    do
    {
        bar = vfoo;  // L.7
    }while(bar!=1);
    return;
}

此代码忙-等待变量变为1。如果第一次通过时vfoo 没有设置为1,我会被卡在里面吗?

此代码在没有警告的情况下编译。 标准对此有何评论?

  • vfoo 被声明为 volatile。因此,对此变量的读取应该进行优化。
  • 但是,bar volatile 合格。是否允许编译器优化对此bar 的写入? 。IE。编译器将对vfoo 进行读取访问,并且允许丢弃该值而不将其分配给bar(在L.7)。
  • 如果这是标准有话要说的特殊情况,能否请您包括该条款并解释标准的律师谈话?

【问题讨论】:

  • 如何“优化写入bar”?为了做到这一点,它写入的值必须提前知道,但该值是加载vfoo 的结果,因为vfoo 是易变的,所以不能省略。
  • "如果第一次通过时 vfoo 没有设置为 1,我会不会被卡在里面。"你如何测试它?
  • 1) 正确。 2) bar 的类型无关紧要,不允许编译器优化vfoo 的读取。 3) 不,这不是特例,并且 100% 定义明确。

标签: c language-lawyer volatile non-volatile


【解决方案1】:

标准对此的规定包括:

5.1.2.3 程序执行

¶2 访问 volatile 对象、修改对象、修改文件或调用执行任何这些操作的函数都是副作用,即执行环境状态的变化。表达式的评估通常包括值计算和副作用的启动。左值表达式的值计算包括确定指定对象的身份。

¶4 在抽象机中,所有表达式都按照语义指定的方式进行评估。如果一个实际的实现可以推断出它的值没有被使用并且没有产生所需的副作用(包括调用函数或访问易失性对象引起的任何副作用),则它不需要评估表达式的一部分。

¶6 对一致性实现的最低要求是:

  • 对 volatile 对象的访问严格按照抽象机的规则进行评估。
  • ...

¶2 的要点尤其应该是访问 volatile 对象与调用 printf 之类的操作没有什么不同——它不能被忽略,因为它有副作用。想象一下你的程序将bar = vfoo; 替换为bar = printf("hello\n");

【讨论】:

  • 与调用printf类似”更像是在此处调用scanf
  • @curiousguy:任何一个都有无法消除的副作用;这就是重点。 scanf 是一个更接近易变负载的语义模拟,但我有点试图避免语义争论,而是专注于“有副作用”的更高层次的概念。
【解决方案2】:

volatile 变量必须在任何访问时被读取。在您的代码 sn-p 中,无法优化读取的内容。编译器知道bar 可能会受到副作用的影响。所以条件会被正确检查。

https://godbolt.org/z/nFd9BB

【讨论】:

  • 这只是 gcc 很好吗?或标准要求的 100% 万无一失。
  • 正如我所写 - 它无法优化非易失性变量,因为它也容易产生副作用。
  • @P__J__ 你能引用 C 标准吗?我不是 C 方面的专家,但在 C++ 中情况会完全不同。
  • @KonradRudolph 100% 不同意。你能引用 C++ 标准吗?
  • @Gerd:你对引用这个词感到困惑。将从 volatile 对象加载的值存储在其他地方不是引用它。您引用的语言是关于通过错误的非易失性类型的覆盖(通过类型双关语)对象非法访问易失性对象。例如。 *(int *)&vfoo.
【解决方案3】:

但是,bar 不是 volatile 限定的。

变量bar 用于保存一个值。您关心其中存储的值,还是关心该变量是否完全根据 ABI 表示?

Volatile 会向您保证后者。你的程序依赖于前者。

是否允许编译器优化对该栏的写入?

当然。为什么您可能会关心读取的值是否真的被写入分配给堆栈上变量的内存位置?

您所指定的只是将读取的值作为退出条件进行测试:

        bar = ...
    }while(bar!=1);

.即编译器将对 vfoo 进行读取访问,并且允许 丢弃该值,不将其分配给 bar(在 L.7 中)。

当然不是!

编译器需要保留通过 volatile 读取获得的值足够的时间,以便能够将其与 1 进行比较。但没有更多时间了,因为您以后再也不会使用 bar

可能是一个奇怪的 CPU 作为条件寄存器中的 EQ1(“等于 1”)标志,每当加载等于 1 的值时就会设置该标志。那么编译器甚至不会临时存储读取的值,而只是 EQ1 条件测试。

根据您的假设编译器可以丢弃所有非易失性变量的变量值,非易失性对象几乎没有可能的用途

【讨论】:

    猜你喜欢
    • 2023-02-10
    • 1970-01-01
    • 1970-01-01
    • 2018-01-18
    • 1970-01-01
    • 2015-11-07
    • 2019-11-07
    • 2023-03-21
    • 1970-01-01
    相关资源
    最近更新 更多