记录一下有关编译的问题
1.条件转移和条件传送
编译器在编译条件控制的代码时,会采用两种策略进行编译。一个是条件转移(条件跳转),一个是条件传送。
条件转移:用条件跳转指令jmp等来完成编译。类似于goto,会跳过部分代码
movq $0,%rax jmp .L1 //条件跳转指令,跳转到.L1 movq (%rax),%rad .L1: popq %rdx;
条件传送:利用条件转移指令cmove等来完成编译,会计算条件操作的两种结果,然后根据条件来选取。可能会跳过条件转移指令后内容的改变(仍会判断,但是不满足条件则什么也不做进入下一指令)
cmpq %rsi,%rdi cmovge %rdx,%rax //满足rdi内容大于等于rsi,则执行move rdx,rax ret
两种编译方式的不同是,条件转移不按指令顺序进行编译,而条件传送则按代码顺序进行编译。
由于现代处理器使用流水线pipelining来获得高性能,在处理上一条指令的过程中可能会同时处理下一条指令(cpu在等待上一条内容读取内存时,会先命令将下一条指令读到内存或者确定指令类型等操作)。由于条件转移在条件未判断完成时不确定下一条指令,所以会造成运行效率降低。(一般计算机会猜测判断条件来执行指令,如果猜错则会回退比较耗时)。而条件转移是顺序执行,不存在这种情况。所以条件传送效率较高。
但是使用条件传送的条件很苛刻,如果两个表达式中任意一个可能产生错误条件或副作用就会导致非法行为,同时如果两个表达式中特别复杂需要大量工作,则编译器也不会使用这种编译方式。
下面是错误行为的示范:
long cread(long *xp){ return xp?*xp:0); //使用条件传送会导致错误行为,因为先计算*xp在,检验xp是否存在 //转换为汇编语言之后,更容易发现这个问题 cread: movq (%rdi),%rax //直接对空指针解引用 testq %rdi,%rdi
下面是副作用行为的示范:
static int x; static int y; int cread(){ return x>y?++x:++y;} //使用条件转移会使两个静态变量的值都+1而不是较大的+1;