【问题标题】:Understanding GCC's un-optimized assembly for UB n = ++n + ++n - why increment twice before shifting?了解 GCC 对 UB n = ++n + ++n 的未优化程序集 - 为什么在移位之前增加两次?
【发布时间】:2022-01-18 00:33:49
【问题描述】:

我知道这是未定义的行为,实际上没有人编写这样的代码。不过我很好奇编译器会对这段代码做什么。

int n = 3;
n = ++n + ++n;

我同时使用 clang 和 gcc 进行编译以进行比较。 没有优化。这是从 clang 生成的程序集:

# clang -O0
movl    $3, -4(%rbp)
movl    -4(%rbp), %ecx
addl    $1, %ecx
movl    %ecx, -4(%rbp)
movl    -4(%rbp), %edx
addl    $1, %edx
movl    %edx, -4(%rbp)
addl    %edx, %ecx
movl    %ecx, -4(%rbp)

它将3复制到一个寄存器中,递增它,然后再次复制这个递增的值并再次递增它,然后将(3+1) + (3+1+1)相加。这看起来很简单。

但是,我无法理解 GCC 正在做什么。这是生成的程序集:

# gcc -O0
movl    $3, -4(%rbp)
addl    $1, -4(%rbp)
addl    $1, -4(%rbp)
sall    -4(%rbp)

据我了解,它递增两次,然后左移一次(sall),即乘以 2。

我认为它注意到++n 在操作数的两边是相同的,所以它把它当作公因数。但是在那种情况下,为什么它会增加两次

Clang 的版本给出了9,GCC 给出了10。 (考虑到 UB,任何结果都是可以接受的,但这证实了编译器内部逻辑的最终结果实际上是不同的。)

谁能解释一下 GCC 在这里试图完成什么?

【问题讨论】:

  • “我明白这是未定义的行为”——然后走开。试图理解为什么未定义的行为会如此表现是没有意义的。
  • 不,我知道这是未定义的行为,我试图通过查看程序集来了解 GCC 试图完成的工作。
  • 数字电子学中有一个术语叫做“无关紧要”——它是输入的组合,不应该发生。它非常有用,因为在这种情况下,逻辑可以做任何事情,以便针对合法输入进行优化。同样在这里,这段代码是“无关紧要的”,编译器正在做一些事情,如果代码是合法的,则会产生更优化的输出。
  • 我认为由于此处未定义操作顺序,因此 GCC 会评估 ++ 两次,然后将结果值相加优化 n + n = 2n
  • 没有人能理解,因为没有人能知道这一行的作用,因为它是 Undefined Behaviour。

标签: c assembly gcc x86-64 undefined-behavior


【解决方案1】:

一元++ 运算符表示它的操作数在计算之前要递增。 Clang 会这样解释您的表达式:

n = n + 1
tmp1 = n
n = n + 1
tmp2 = n
n = tmp1 + tmp2

而 GCC 会做这样的事情,在下降到表达式之前处理预增量:

n = n + 1
n = n + 1
tmp1 = n
tmp2 = n
n = tmp1 + tmp2

然后,意识到+ 的两个操作数是相同的表达式,它会执行强度降低产生

n = n + 1
n = n + 1
n = n << 1

尽管缺少优化标志,但很可能会执行这种强度降低,因为已知 GCC 会在优化标志影响结果之前的编译过程的早期执行某些强度降低。

但请注意,结果可能会因编译器选项的不同而有所不同。

【讨论】:

  • 好的,我接受这个,因为它实际上回答了我的问题。是的,我知道它可以改变,我只是在询问这个特殊情况。谢谢。
  • 我不会说“在优化标志影响结果之前”。例如在-O0,它仍然优化n / 10 以使用乘法逆。但是-Os 将使它使用idiv。更像-O0 不代表没有优化;对于 GCC 旨在通过多种内部表示来转换程序的方式,这是不可能的。 -O0 真正的意思是“编译快速、一致的调试”而不是“没有优化”。它仍然会在单个表达式或语句中做一些本地的事情。请参阅 Basile 的回答:Disable all optimization options in GCC
  • @PeterCordes 将除法转换为乘法并不是这些早期强度降低之一。 AFAIK 它们是在解析器中完成的,这是使用 gcc 进行静态分析的主要问题之一;你只是没有办法得到真正对应源代码的 AST。
猜你喜欢
  • 2019-12-06
  • 1970-01-01
  • 2020-06-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-22
  • 1970-01-01
相关资源
最近更新 更多