assembly c程序汇编输出中if语句

daolsyd0  于 2022-11-13  发布在  其他
关注(0)|答案(6)|浏览(127)

我有一段简单的c:

#include <stdio.h>

void test() {}

int main()
{
    if (2 < 3) {

        int zz = 10;
    }
    return 0;
}

当我看到这段代码的程序集输出时:

test():
  pushq %rbp
  movq %rsp, %rbp
  nop
  popq %rbp
  ret
main:
  pushq %rbp
  movq %rsp, %rbp
  movl $10, -4(%rbp) // space is created for zz on stack
  movl $0, %eax
  popq %rbp
  ret

我从here***(预设选项)取得组件***我看不见条件检查的指令在哪里?

unftdfkk

unftdfkk1#

你看不到它,因为它不在那里。编译器能够执行分析,并且相当容易地看到这个分支将 * 总是 * 被进入。
它不会发出只会浪费CPU周期的检查,而是发出容易优化的代码版本。
一个C程序 * 不是 * 一系列CPU执行的指令。那是发出的机器码。一个C程序是一个行为的描述,你编译的程序 * 应该 *。编译器可以自由地以几乎任何方式翻译它,只要你得到 * 那个行为 *。
这就是所谓的“假设规则”。

col17t5w

col17t5w2#

有趣的是,与其他编译器(ICC和MSVC)不同,gcc和clang即使在-O0 * 上也会优化if() *。
gcc -O0并不意味着没有优化,它意味着除了编译所需的优化之外,根本没有 * 额外 * 的优化。但是gcc在发出asm之前,必须通过函数逻辑的几个内部表示进行转换。(GIMPLE和寄存器传输语言)。gcc没有一个特殊的“哑模式”,它盲目地将每个C表达式的每一部分转换为asm。
即使是像TCC这样的超级简单的一遍编译器也会在表达式(甚至语句)中进行一些小的优化,比如意识到一个总是为真的条件不需要分支。
gcc -O0是默认值,您显然使用了它,因为到zz的死存储没有被优化掉。
gcc -O0旨在快速编译,给予一致的调试结果

  • 每个C变量都存在于内存中,不管它是否被使用过。
  • 在C语句之间的寄存器中不保存任何内容(声明为register的变量除外; -O0是关键字唯一执行任何操作的时候)。因此,您可以在单步执行时使用调试器修改任何C变量。即,在单独的C语句之间溢出/重新加载所有内容。另请参见 * Why does clang produce inefficient asm with -O0 (for this simple floating point sum)? *(这就是为什么-O0的基准测试是毫无意义的:用更少的更大表达式编写相同的代码只在-O0下更快,而在像-O3这样的真实的设置下则不然)。

其他有趣的结果:常量传播不起作用,请参见 * Why does integer division by -1 (negative one) result in FPE? *,了解gcc使用div作为设置为常量的变量的情况,而不是使用更简单的方法作为文字常量。

  • 每个语句都是独立编译的,所以你甚至可以使用GDB将jump编译到不同的源代码行(在同一个函数中),并得到一致的结果。(不像在优化代码中那样,这可能会崩溃或给予无用的结果,而且肯定不匹配C抽象机)。

考虑到gcc -O0行为的所有这些要求,if (2 < 3)仍然可以被优化为零asm指令。该行为不依赖于任何变量的值,并且它是一个单一的语句。它不可能不被采用,所以编译它的最简单的方法是不使用指令:漏入if{ body }
请注意,gcc -O0的规则/限制远远超出了C的as-if规则,即函数的机器代码只需实现C源代码的所有外部可见行为。gcc -O3将整个函数优化为

main:                 # with optimization
    xor    eax, eax
    ret

因为它并不关心为每个C语句保留asm。

其他编译器:

查看Godbolt上的所有4个主要x86编译器。
clang -O0通常更接近于C到asm的音译,例如它会使用div来代替shift,而不是使用x / 2来代替shift,而gcc即使在-O0上也使用a multiplicative inverse for division by a constant,但是在这种情况下,clang还决定没有足够的指令来满足always-true条件。
ICC和MSVC都为分支发出asm,但它们实际上都执行0 != 1,而不是您可能期望的mov $2, %ecx/cmp $3, %ecx,原因不明:

# ICC18
    pushq     %rbp                                          #6.1
    movq      %rsp, %rbp                                    #6.1
    subq      $16, %rsp                                     #6.1

    movl      $0, %eax                                      #7.5
    cmpl      $1, %eax                                      #7.5
    je        ..B1.3        # Prob 100%                     #7.5

    movl      $10, -16(%rbp)                                #9.16
..B1.3:                         # Preds ..B1.2 ..B1.1
    movl      $0, %eax                                      #11.12
    leave                                                   #11.12
    ret                                                     #11.12

MSVC使用xor-zeroing窥视孔优化,即使没有启用优化。
了解编译器在-O0环境下执行哪些局部/窥视孔优化是一件很有趣的事情,但它并没有告诉您有关C语言规则或代码的任何基本信息,它只是告诉您编译器的内部结构,以及编译器开发人员在花时间寻找简单优化与在非优化模式下更快地编译之间所做的权衡。

**asm绝不会以任何允许反编译器重构C源代码的方式忠实地表示C源代码。**只是为了实现等效的逻辑。

iugsix8n

iugsix8n3#

很简单,它不存在,编译器优化了它。
以下是使用gcc编译而不进行优化时的程序集:

.file   "k.c"
    .text
    .globl  test
    .type   test, @function
test:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    nop
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   test, .-test
    .globl  main
    .type   main, @function
main:
.LFB1:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $10, -4(%rbp)
    movl    $0, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE1:
    .size   main, .-main
    .ident  "GCC: (Debian 6.3.0-18) 6.3.0 20170516"
    .section    .note.GNU-stack,"",@progbits

这里是优化:

.file   "k.c"
    .text
    .p2align 4,,15
    .globl  test
    .type   test, @function
test:
.LFB11:
    .cfi_startproc
    rep ret
    .cfi_endproc
.LFE11:
    .size   test, .-test
    .section    .text.startup,"ax",@progbits
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB12:
    .cfi_startproc
    xorl    %eax, %eax
    ret
    .cfi_endproc
.LFE12:
    .size   main, .-main
    .ident  "GCC: (Debian 6.3.0-18) 6.3.0 20170516"
    .section    .note.GNU-stack,"",@progbits

正如你所看到的,不仅仅是比较被优化掉了,几乎整个main都被优化掉了,因为它没有产生任何可见的东西。变量zz从未被使用过。你的代码所做的唯一可观察的事情就是返回0。

cgyqldqp

cgyqldqp4#

2总是小于3,因此,由于编译器知道2〈3的结果总是真的,所以在汇编程序中不需要if判断。
优化意味着生成更少的时间/代码。

7d7tgy0s

7d7tgy0s5#

if (2<3)

始终为true,因此编译器不会为其发出操作码。

dbf7pr2w

dbf7pr2w6#

条件if (2<3)always true。所以一个优秀的编译器会检测到这一点,并生成代码,就好像条件不存在一样。实际上,如果你用-O3优化它,godbolt.org只会生成:

test():
  rep ret
main:
  xor eax, eax
  ret

这也是有效的,因为只要保留了 * 可观察的行为 *,就允许编译器优化和转换代码。

相关问题