这一节我们来讨论递归函数栈帧实现。
一:mov和lea的含义和用法
在论述递归栈帧之前,回到第一节(1)中关于mov的阐述,说实话,刚开始写这些文字的时候,我以为自己理解了mov的含义,但是当看到lea操作时,大脑却瞬间混乱,连之前认为熟练的mov也忽然打不到方向了。因此这里有必要再次详细阐述下mov和lea的实际含义,以免在后面的讨论中混淆。
先回忆下mov(数据传送指令),我提到过有两种用法,一种是值传递:mov $4, %eax和 mov %ebx,%eax。 都是把数值4或者寄存器ebx里的数值传送到寄存器eax;
第二种用法是间接引用,如mov 4(%ebx), %eax,就是用诸如"()"的方式把寄存器里的数值当成地址理解,并根据这个地址寻找到存储器的相应位置,并读出值,再传送给%eax。
关于间接引用,汇编和C语言在概念上完全一致,只是在实现方式上有所不同。我们熟悉的C语言在定义变量时,把存储地址的变量和存储数值的变量区分开来,于是才有无符整型变量和无符整型指针变量u_int a与u_int *a的区别,事实上在32位系统中他们都是占领4字节空间,并且存储的都是32位无符整数,只是在一些算术语句和数组运算以及“*”等操作时能体现不同的处理策略。而在汇编语言中,寄存器就是寄存器,不存在指针寄存器或整数寄存器的区别(之前的栈指针%esp和帧指针%ebp只是编译器设计时为了便于理解而约定俗成的名称指定)。寄存器里面可以存储数值,至于这个数值是地址还是其他,对CPU来说并不重要,只是当编译器觉得他存的是地址并希望找到相应的存储位置时,就可以利用“()”操作符,像C语言中的“*”那样,进行间接寻址罢了。C语言之所要定义出指针变量,一是为了使得间接寻址更直观好理解,二是特定类型按约定长度跳步更方便(和数组类似)。但即便这样,埋怨C指针和数组概念抽象的小伙伴仍是人山人海……
清楚了间接寻址,我们再来看lea(加载有效地址Load Effective Address),好高大上的名字,咋一看感觉这又是要把间接寻址玩翻天的操作,其实没那么麻烦。
看看上面所说的mov如何处理间接寻址实现数据传送的?分为三步:
1、计算地址值,要么直接给常数,要么直接是寄存器里的值,要么对寄存器里的值进行一系列运算得出的值;
2、根据得到的地址值引用其空间中的数值;
3、将得到的数值传送给接收方(寄存器)
上面说的是mov,那lea是想干什么伟大工程呢?其实它就是省略第2步操作,直接从1跳到3,解释完毕!举例如下:
假设%ebx的值是0x108,而0x108是某个存储器地址,里面存数值0xCD,那么如下两条语句:
movl (%ebx), %eax //1、从ebx中读出地址值0x108;2、根据地址0x108间接寻址到存储器,并将其中的0xCD挖出来;3、将0xCD传送给eax
leal (%ebx), %eax //1、从ebx中读出地址值0x108;2、对不起,作为lea我不干这一步嚯嚯呵呵哈哈………… ;3、将0x108传送给eax
简单吧?如果说mov可以间接寻址引用,那lea无非就是寻址,但不引用。其实另一种理解方式是,lea中的"