c++ - 后增量行为

标签 c++ c post-increment

最近,我将我的项目从 gcc 4.3 升级到 gcc 5.5。在那之后,我看到后增量运算符的行为发生了变化,这导致了我的项目出现问题。我使用全局变量作为控制变量。例如,考虑这个示例程序:

int i = 0;
int main()
{
        int x[10];

        x[i++] = 5; ===> culprit

        return 0;

}

在上面的代码片段中,只有在将 5 分配给 x[0] 之后,i 的值才应该递增,这样可以保证x[0]i 递增之前分配了一个正确的有效值。

现在问题来了,我看到在迁移到 gcc 5.5 之后,汇编指令发生了变化,甚至在赋值发生之前 i 的值就已经增加了。上述片段的汇编指令:

Dump of assembler code for function main():
6       {
   0x0000000000400636 <+0>:     push   %rbp
   0x0000000000400637 <+1>:     mov    %rsp,%rbp

7               int x[10];
8
9               x[i++] = 1;
   0x000000000040063a <+4>:     mov    0x200a00(%rip),%eax        # 0x601040 <i>
   0x0000000000400640 <+10>:    lea    0x1(%rax),%edx
   0x0000000000400643 <+13>:    mov    %edx,0x2009f7(%rip)        # 0x601040 <i>  ====> i gets incremented here
   0x0000000000400649 <+19>:    cltq
   0x000000000040064b <+21>:    movl   $0x5,-0x30(%rbp,%rax,4)    =====> x[0] is assigned value here

10
11              return 0;
   0x0000000000400653 <+29>:    mov    $0x0,%eax

12
13      }
   0x0000000000400658 <+34>:    pop    %rbp
   0x0000000000400659 <+35>:    retq

由于上述程序集,进程中使用变量 i 的另一个线程开始从全局数组中读取不正确的值。

现在,当使用 gcc 4.3 编译时,相同的代码遵循我理解的行为,即首先分配值,然后递增 i。使用 gcc 4.3 对同一代码段进行汇编说明:

Dump of assembler code for function main():
5       int main()
   0x00000000004005da <+0>:     push   %rbp
   0x00000000004005db <+1>:     mov    %rsp,%rbp

6       {
7               int x[10];
8
9               x[i++] = 1;
   0x00000000004005de <+4>:     mov    0x200a64(%rip),%edx        # 0x601048 <i>
   0x00000000004005e4 <+10>:    movslq %edx,%rax
   0x00000000004005e7 <+13>:    movl   $0x5,-0x30(%rbp,%rax,4)     ======> x[0] gets assigned here
   0x00000000004005ef <+21>:    lea    0x1(%rdx),%eax
   0x00000000004005f2 <+24>:    mov    %eax,0x200a50(%rip)        # 0x601048 <i>   ======> i gets incremented here

10
11              return 0;
   0x00000000004005f8 <+30>:    mov    $0x0,%eax

12
13      }
   0x00000000004005fd <+35>:    leaveq
   0x00000000004005fe <+36>:    retq

我想知道这是否是新编译器的预期行为?有没有我可以切换回旧行为的开关?或者这是新编译器中存在的错误?

任何帮助或线索将不胜感激。

注意:由于性能问题,我想避免在读取 i 期间发生锁定。上面代码中的 culprit 行在锁中执行。因此,在任何时候都只有一个线程可以更新 i,但是由于编译器中汇编指令的更改,引入了竞争条件而无需更改任何代码。

编辑 1:我知道存在锁定问题,我也将其保留为一个选项,但我真正想知道的是是否有任何开关或标志可以使用,我可以恢复到旧的行为。代码库非常非常庞大,我将不得不遍历整个代码库以检查代码中的其他位置是否存在类似问题。因此,恢复旧行为将是救命稻草。

最佳答案

您误解了增量后提供的保证。它保证x所在的位置存储将使用 i 的旧值进行计算.它绝对不保证 x将在更新值存储在 i 之前存储.

编译器完全可以自由地将代码转换为:

int temp = i;
i = temp+1;
x[temp] = 5;

此外,如果您有一个线程正在修改 i和另一个线程阅读 i ,您无法保证其他线程将看到哪个值。你甚至不能保证它将看到新值或旧值,除非i。是std::atomic .

鉴于您正在尝试同时更新 ix以协调的方式,您将必须拥有一把锁。

编译器甚至可以将您的代码转换为:

i = i + 1;
// <<<<
x[i-1] = 5;

如果另一个线程跳入并修改 i,这很有趣在标记为 <<<< 的点.

关于c++ - 后增量行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50758345/

相关文章:

c++ - 在 C++ 中使用 C 库

c - 现代处理器上的内存对齐?

c++ - 后增量和前增量概念?

c++ - 错误 : expected constructor, 析构函数,或 ';' token 之前的类型转换?

c++ - 实体组件系统困惑

c++ - 制作某种菜单时出现问题

c - "char *p, *getenv();"有什么作用?为什么函数的变量声明行中没有参数?

c - 为什么即使大小相同,unsigned short int 也会被提升为 unsigned int?

c++ - 递归函数参数中前后递减的区别

javascript - 在 C# 中使用 `x += 1` 而不是 `++x` 是否有任何副作用?