C 了解组装:-O2如果分支

C 了解组装:-O2如果分支,c,assembly,disassembly,C,Assembly,Disassembly,我刚刚用一个if语句尝试了一个简单的C程序,并分析了它的程序集。然而,当-O2标志用于编译时,它的行为有很大不同 相同的C代码为:- #include<stdio.h> int main(int argc, char **argv) { int a; if(a<0) { printf("A is less than 0\n"); } } 我读到test指令基本上只执行两个操作数的逻辑AND。我还了解到,js指令在上一条指令中的符号发

我刚刚用一个if语句尝试了一个简单的C程序,并分析了它的程序集。然而,当-O2标志用于编译时,它的行为有很大不同

相同的C代码为:-

#include<stdio.h>

int main(int argc, char **argv) {
    int a;

    if(a<0) {
        printf("A is less than 0\n");
    }
}
我读到
test
指令基本上只执行两个操作数的逻辑AND。我还了解到,
js
指令在上一条指令中的符号发生变化时执行跳转。因此,
test
ing eax with eax将给出
0
1
,跳转将取决于此

我无法理解它是如何被用于分支的。
有人能解释一下它是如何工作的吗?

我不知道您期望的是什么,但是您的程序有未定义的行为,因为
a
没有初始化。因此,汇编程序的输出可以是任何形式。

如果
eax
为负数,则标志将指示在
test
指令之后进行跳转。这会将电脑推到执行打印的
.L4
。否则,我们离开。

测试eax,如果eax=0,eax
将设置零标志


js指令基本上会检查符号标志(当符号发生变化时,ajs不会跳转,如果符号标志为1则跳转

如果最后一次运算的结果为负数,则符号位为on(2的补语中的负数在1中具有最高有效位)


因此,如果AND运算在两个负整数(-1&-1)之间,则最后一位将为1(符号标志),因此进行跳转。如果数字为正,则最后一位将为0,则不会进行跳转。

看起来,当您指定-O2时,编译器会将“int a”放入寄存器以进行速度优化

由于从未初始化“int a”,程序集不会显示写入eax的任何内容,而是具有上次分配给它的值


其他答案解释了测试是如何作为一种分支机制工作的。

英特尔手册对此很有帮助。以下是它为测试指令编写的文档:


SF是符号标志,由JS操作码测试。它被设置为eax的最高有效位,即符号位。因此,当eax包含负数时,将进行跳转。

test
设置所有算术标志,而不仅仅是零标志。整个程序是否因为
a
的值未定义而未定义罚款?我假设它会使用调用时堆栈上的任何内容。程序的输出可以是printf,也可以是nothing,但为什么编译器的输出会是nothing?理论上,输出可以是nothing,但现实世界中的编译器不会输出无意义的内容。@nmichaels:程序的行为是未定义的,是的。这是整个pro的行为例如,优化器可以很好地得出结论,
a
从未写入,因此其值未定义,因此可以省略
a
的任何后续用法。这将是一个完全有效的优化。@nmichaels:标准不要求调用时堆栈上有任何内容。此uninitialized
a
允许不是由RAM支持,而是由一个巨大的混沌空间支持,这会让世界陷入一种老年恐惧。或者,更现实地说,在某些体系结构上,内存可能包含陷阱表示,这会导致硬件在加载到int寄存器时抛出故障。允许这两种情况(尤其是第二种情况),程序的行为是未定义的。x86不需要这么多自由度,但它在标准中有。一个好的编译器应该优化整个程序,因为它有未定义的行为(使用未初始化的变量)。或者,如果初始化了
a
,则应根据
a
的值优化条件。
main:
    push    %ebp
    mov %ebp, %esp
    sub %esp, 8
    and %esp, -16
    sub %esp, 16
    test    %eax, %eax
    js  .L4
    leave
    ret
    .p2align 4,,15
.L4:
    sub %esp, 12
    push    OFFSET FLAT:.LC0
    call    puts
    add %esp, 16
    leave
    ret
    .size   main, .-main
    .section    .note.GNU-stack,"",@progbits
    .ident  "GCC: (GNU) 3.4.6"