读《Professional Assembly Language》之奇怪的if-then-else
今年是离开校园的第五年,这五年来我一直在从事应用软件的设计、开发工作,大部分时间是在与高级编程语言、设计模式、业务逻辑打交道。它们大多流于表面,久而久之,与技术底层疏远了,诸如计算机组成原理、汇编语言、编译原理、数据结构以及算法慢慢得生疏,时至今日,我已经弄不懂IA-32里有几类寄存器、间接寻址、词法分析是怎么一回事情了。五年是一个契机,趁着下一个五年开始之际,我计划用三个月至半年时想间,重新学习这些知识,以期达到巩固基础,厚积薄发的目的。
学习过程肯定会有问题,找不出问题的学生不是好学生。因此,我把遇到的问题和解决的方法记录下来,便形成了读书笔记。本篇博文便是我在阅读《Professional Assembly Language》一书时,所作的其中一篇读书笔记。《Professional Assembly Language》,中文译作《汇编语言程序设计》,是我学习汇编语言时选择的工具书,该书对于我这种已经有了高级语言的使用经验,又热衷Linux的人来说非常合适。
在本书英文版第149、150页(中文版第117页),作者展示了一段稍复杂的C语言if语句,以及对应的汇编伪代码:
Instead of a single conditional jump instruction, there may be several, with each one evaluating a separate part of the if condition. For example, the C language if statment
if (eax < ebx) || (eax == ecx) thencreate the following assembly language code:
if: cmpl %eax, %ebx jle else cmpl %eax, %ecx jne else then: < then logic code > jmp end else: < else logic code > end:This If statement condition required two separate CMP instructions. Because the logical operator is and OR, if either CMP instruction evaluates to true, the program jumps to the else label.
我比较了中文版对上段内容的翻译,完全符合英文原意,即该判断语句是个“或”运算,因此只要有一个条件为真,则进入到else。
首先,上面最后一段话的逻辑非常奇怪,if-then-else语句应该是,在条件成立时进入到then才对,而不是else。而原文中的逻辑则是我从来没见过的,我认为是作者的笔误。
其次,汇编代码部分,第三行的jle else
似乎不对,如果ebx小于或等于eax,那么第一个条件不成立,则应该继续判断第二个条件,而不是跳转到else;如果ebx大于eax,那么不必判断第二个条件,应该跳转到then。
在假定C代码正确的前提下,我认为使用ja then
才对。为此,我写了一段简单的C程序,并且对照了经GCC编译后的汇编代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | int foo; int main() { int a = 1; int b = 2; int c = 3; if (a < b || a == c) { foo = 100; } else { foo = 200; } return 0; } |
对应的汇编如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | .file "ifthen2.c" .text .globl main .type main, @function main: leal 4(%esp), %ecx andl $-16, %esp pushl -4(%ecx) pushl %ebp movl %esp, %ebp pushl %ecx subl $16, %esp movl $1, -8(%ebp) movl $2, -12(%ebp) movl $3, -16(%ebp) movl -8(%ebp), %eax cmpl -12(%ebp), %eax jl .L2 movl -8(%ebp), %eax cmpl -16(%ebp), %eax jne .L4 .L2: movl $100, foo jmp .L5 .L4: movl $200, foo .L5: movl $0, %eax addl $16, %esp popl %ecx popl %ebp leal -4(%ecx), %esp ret .size main, .-main .comm foo,4,4 .ident "GCC: (GNU) 4.2.4 (Ubuntu 4.2.4-1ubuntu4)" .section .note.GNU-stack,"",@progbits |
这段汇编代码里,--第十六至二十一行,操作数的位置恰好与示例代码相反,因此这里使用的是jl,而修正后的示例代码使用的是ja。
如果把两个代码中任意一个调换两个操作数的前后位置,则条件跳转指令就应该相同了。这也算是佐证了我的观点。