读《Professional Assembly Language》之奇怪的if-then-else

Professional Assembly Lanauge

Professional Assembly Lanauge

今年是离开校园的第五年,这五年来我一直在从事应用软件的设计、开发工作,大部分时间是在与高级编程语言、设计模式、业务逻辑打交道。它们大多流于表面,久而久之,与技术底层疏远了,诸如计算机组成原理、汇编语言、编译原理、数据结构以及算法慢慢得生疏,时至今日,我已经弄不懂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) then

create 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。

如果把两个代码中任意一个调换两个操作数的前后位置,则条件跳转指令就应该相同了。这也算是佐证了我的观点。

Leave a comment

Your comment