问题 在C中增加volatile变量


考虑以下三个表达式:

++x;
x += 1;
x = x + 1;

据我所知,它们在语义上是相同的,忽略了C ++中的运算符重载。然而,今天我读到了一个断言,它们是不同的,特别是在何时 x 宣布 volatile

为了测试这个断言,我编写了以下内容并为PowerPC,AMD64,ARMv6和68k编译了它:

#include <stdint.h>

static volatile uint64_t x = 0;

void a(void)
{
    ++x;
}

void b(void)
{
    x += 1;
}

void c(void)
{
    x = x + 1;
}

在所有这四个平台上,这三个函数产生相同的汇编输出,无论是在-O1还是-O3。在AMD64上,这只是两条指令:

incq    _x(%rip)
retq

因此,  那个断言背后有什么真相吗?如果是这样,有什么区别,我该如何揭露它?

NB:我完全清楚这一点 volatile 不保证原子性。这不是我在这里要求的 - 除非原子性本身在三者之间有所不同。


11576
2017-09-22 17:05


起源

C11标准草案, 6.5.3.1 Prefix increment and decrement operators Section 2 [...]The expression ++E is equivalent to (E+=1).[...]。 6.5.16.2 Compound assignment, Section 3 [...]A compound assignment of the form E1 op = E2 is equivalent to the simple assignment expression E1 = E1 op (E2), except that the lvalue E1 is evaluated only once[...] - EOF
@EOF OP可能对第三种情况更感兴趣,因为表达式中似乎存在读取和写入访问,这应该反映在机器代码中...即将检查
C11§6.5.16/ 3的脚注: 111) The implementation is permitted to read the object to determine the value but is not required to, even when the object has volatile-qualified type. - Kninnug
好的,只有这个脚注澄清这不是编译器的错误;)(并且周围有很多 volatile)。我还没知道另一个实现定义的情况......
@Kninnug:很有意思。但是我读这个的方式,就像一个表达式一样 b = a = whatever,进一步使用正确分配的价值。 - EOF


答案:


从草案C ++标准部分 5.3.2  [expr.pre.incr] 说:

如果x不是bool类型,则表达式++ x等效于x + = 1

5.17  [expr.ass] 说:

E1 op = E2形式的表达式的行为等同于   E1 = E1操作E2除了E1仅被评估一次。

所以 ++x 和 x += 1 是等价的。

现在一个案例在哪里 x += 1 不同于 x = x + 1 就是它 E1 仅评估一次。在这种特殊情况下,它并不重要,但我们可以提出一个案例:

#include <stdint.h>

volatile uint64_t x = 0;
volatile uint64_t y[2] = {0} ;

void c(void)
{
   y[x] = y[x] + 1;
}

在这种情况下 x 将被评估两次,而不是这种情况:

void b(void)
{
   y[x] += 1;
}

和a godbolt会议显示 对于 b()

b():                                  # @b()
movq    x(%rip), %rax
incq    y(,%rax,8)
retq

并为 c()

c():                                  # @c()
movq    x(%rip), %rax
movq    y(,%rax,8), %rax
incq    %rax
movq    x(%rip), %rcx
movq    %rax, y(,%rcx,8)
retq

据我所知,这也适用于C11。从C11部分 6.5.3.1 前缀增量和减量运算符:

表达式++ E等价于(E + = 1)。

从部分 6.5.16.2 复合赋值:

形式E1 op = E2的复合赋值相当于简单   赋值表达式E1 = E1 op(E2),除了左值为E1   仅评估一次


11
2017-09-22 17:50



有趣的是它说“如果不是bool型”。如何定义运算符重载的类型? (是的,我很迂腐。) - Chromatix
@Chromatix重载运算符将转换为函数调用,并将取决于实现,因此标准无法为这些情况提供保证。 - Shafik Yaghmour
关于布尔案的@Chromatix标准说 The operand of prefix ++ is modified by adding 1, or set to true if it is bool (this use is deprecated). 所以它严格地说是等价的,它是折旧的用法。 - Shafik Yaghmour
我已将数组访问测试用例添加到我的程序中,并且它们还在其他体系结构上生成彼此不同的代码。有趣的是,地址计算 y[x] = y[x] + 1 在RISC平台上进行流水线操作,以便获取 x 两者都发生了 之前 获取 y[x]。 - Chromatix
@Chromatix我注意到gcc做了同样的事情,但clang没有,我不知道该怎么想。 - Shafik Yaghmour


在抽象语义中,所有这三个表达式都完全相同。他们 访问  x 检索其值,计算新值,然后将更新后的值存储回来 x。有一个访问和商店。 (表达式也会产生一个被丢弃的值)。

虽然 x = x + 1 提到 x 两次,左侧 x 没有评估。也就是说,并非完全:它  没有计算。仅根据确定指定值的位置来评估它。

所以这里可能会对位置进行双重评估:左侧确定位置 x 右侧也是如此。但确定位置不涉及访问位置本身。

对于某些类型的表达式,确定位置确实涉及访问值。例如:

a[i] = a[i] + 1;

这是完全不同的

i = i + 1

因为 i 这里只是一个辅助变量,必须知道其值才能确定存储位置 a[i] (和 i 本身甚至不会增加)。如果 i 是 volatile,然后是两个抽象访问它 a[i] = a[i] + 1 必须对应两个实际访问。


0
2017-09-22 19:05