当前位置: 首页 > 知识库问答 >
问题:

编译过程中琐碎的(没有效果的代码)代码什么时候会被删除?

宰父单弓
2023-03-14
volatile int num = 0;
num = num + 10;

上面的C代码似乎在英特尔汇编中生成了以下代码:

mov DWORD PTR [rbp-4], 0
mov eax, DWORD PTR [rbp-4]
add eax, 10
mov DWORD PTR [rbp-4], eax

如果我把C代码改成

volatile int num = 0;
num = num + 0;

为什么编译器不会生成汇编代码:

mov DWORD PTR [rbp-4], 0
mov eax, DWORD PTR [rbp-4]
add eax, 0
mov DWORD PTR [rbp-4], eax

gcc7。2-O0省去了添加eax,0,但所有其他指令都是相同的(Godbolt)。

这种琐碎的代码在编译过程的哪一部分被删除。是否有任何编译器标志会使GCC编译器不进行此类优化。

共有2个答案

楚勇
2023-03-14

根据C中的"as if"规则,实现可以自由地做任何它想做的事情,只要可观察到的行为符合标准。

... 一致性实现需要模拟(仅)抽象机器的可观察行为,如下所述。

这一规定有时被称为“好像”规则,因为实现可以自由地忽略本国际标准的任何要求,只要结果是好像要求已经得到遵守,只要可以从程序的可观察行为中确定。

例如,如果一个实际的实现可以推断出表达式的值没有被使用,并且没有产生影响程序可观察行为的副作用,那么它就不需要评估表达式的一部分。

至于如何控制gcc,我的第一个建议是使用-O0标志关闭所有优化。通过使用各种-f,您可以获得更精细的控制

越源
2023-03-14

clang将在-O0处发出add eax,0,但gcc、ICC和MSVC都不会。见下文。

gcc-O0并不意味着"没有优化",gcc没有一个"braincad字面翻译"模式,它试图将每个C表达式的每个组件直接音译成一个ash指令。

GCC的-O0并不打算完全不优化。它的目的是“快速编译”,并使调试产生预期的结果(即使使用调试器修改C变量,或跳转到函数中的另一行)。因此,它会溢出/重新加载每个C语句周围的所有内容,假设在此类块之前停止的调试器可以异步修改内存。(关于结果的有趣例子,以及更详细的解释:为什么整数除以-1(负1)会导致FPE?)

对于gcc-O0来说,编写速度更慢的代码的需求并不大(例如,忘记了0是加法标识),所以还没有人为此实现一个选项。如果这种行为是可选的,它甚至可能会使gcc变慢。(或者可能有这样一个选项,但默认情况下,即使在-O0,它也是开启的,因为它速度快,不会影响调试,而且很有用。通常人们喜欢他们的调试版本运行得足够快,可以使用,尤其是对于大型或实时项目。)

正如@Basile Starynkevitch在《禁用GCC中的所有优化选项》中解释的那样,在制作可执行文件的过程中,gcc总是通过其内部表示进行转换。仅仅这样做就会导致某些类型的优化。

例如,即使在-O0中,gcc的除以常数算法使用定点乘法逆或移位(对于2的幂)来代替idv指令,但是clang-O0将使用idv来表示x/=2

在这种情况下,Clang的-O0优化程度也低于gcc:

void foo(void) {
    volatile int num = 0;
    num = num + 0;
}

x86-64的锁紧螺栓上的asm输出

    push    rbp
    mov     rbp, rsp

    # your asm block from the question, but with 0 instead of 10
    mov     dword ptr [rbp - 4], 0
    mov     eax, dword ptr [rbp - 4]
    add     eax, 0
    mov     dword ptr [rbp - 4], eax

    pop     rbp
    ret

正如您所说,gcc去掉了无用的addeax,0。ICC17多次存储/重新加载。在调试模式下,MSVC通常非常直白,但即使它也避免发出addeax,0

Clang也是Godbolt上4个x86编译器中唯一一个将idiv用于返回x/2的编译器 。其他的都是SAR CMOV或其他实现C符号除法语义的工具。

 类似资料:
  • 问题内容: 这段代码使我凝视了几分钟: (这里的第137行) 我以前从未见过,而且我也不知道Java有一个“ loop”关键字(NetBeans甚至没有像关键字一样给它上色),并且它在JDK 6中可以很好地编译。 有什么解释? 问题答案: 它不是一个keyword,而是一个label。 用法:

  • 我们目前在Java8中编译了代码,但我们在Java11 VM上运行它。现在我们也在尝试将代码移动到Java11编译时。想知道在Java8中编译代码与在Java11中编译代码在性能方面是否有任何好处,因为两个编译器都会生成不同的类文件(字节码)?在效率方面,一个与另一个有何不同?

  • 问题内容: 为什么要编译Python脚本?您可以直接从.py文件运行它们,并且效果很好,那么在性能上有什么优势吗? 我还注意到,我的应用程序中的某些文件被编译为.pyc,而另一些则没有,为什么? 问题答案: 它被编译为字节码,可以更快,更快速地使用。 无法编译某些文件的原因是,每次运行脚本时都会重新编译与之一起调用的主脚本。所有导入的脚本将被编译并存储在磁盘上。 Ben Blank的 重要补充:

  • PHP代码的编译 PHP是解析型高级语言,事实上从Zend内核的角度来看PHP就是一个普通的C程序,它有main函数,我们写的PHP代码是这个程序的输入,然后经过内核的处理输出结果,内核将PHP代码"翻译"为C程序可识别的过程就是PHP的编译。 那么这个"翻译"过程具体都有哪些操作呢? C程序在编译时将一行行代码编译为机器码,每一个操作都认为是一条机器指令,这些指令写入到编译后的二进制程序中,执行

  • 1.1. 代码编译 1.1.1. Openwrt编译 1.1.2. Kernel编译 1.1.3. Uboot编译 1.1.4. VSP编译 1.1. 代码编译 1.1.1. Openwrt编译 作为Kamino18 YODAOS的整体编译环境,使用openwrt可以编译出系统正常运行所需的主要image如下: 镜像名字 镜像运行位置 镜像说明 镜像生成位置 mcu.bin MCU The ima

  • 请检查这段代码,看看@Arun R在如何计算覆盖另一个矩形的矩形面积中所说的算法有什么问题 为什么它没有删除其他内部的矩形