在C中递增一个易失性变量



考虑以下三个表达式:

++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不能保证原子性。这不是我要问的,除非原子性本身是三者之间的区别

来自c++标准草案5.3.2 [expr.pre.]增加]说:

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

5.17 [expr。屁股]说:

形式E1 op = E2的表达式的行为等价于E1 = E1 op E2, E1只求值一次

所以++xx += 1是等价的

现在x += 1x = 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;
}

和一个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)。

复合赋值:

形式为E1 op= E2的复合赋值等价于简单赋值赋值表达式E1 = E1 op (E2),除了左值E1是只求值一次

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

尽管x = x + 1两次提到x,但左侧的x没有被计算。也就是说,不完全:它的不计算。仅在确定所赋值的位置范围内对其求值。

所以这里可能存在位置的双重求值:左边决定x的位置,右边也决定CC_21的位置。但是确定位置并不需要访问位置本身。

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

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

这与

完全不同
i = i + 1

,因为i在这里只是一个次要变量,为了确定a[i]的存储位置,必须知道它的值(i本身甚至没有被增加)。如果ivolatile,那么在a[i] = a[i] + 1中对它的两次抽象访问必须对应于两次实际访问。

相关内容

最新更新