Trade Off

supercalifragilisticexpialidocious

CSAPP学习笔记第二辑

虽然可以使用优化编译器,但对于严谨的程序员来说,能够阅读和理解汇编代码仍然是一项很重要的技能。

gcc -O2 -o p p1.c p2.c

-O2是告诉编译器使用第二级优化。第二级优化是性能优化和使用方便之间的一种很好的妥协。 程序计数器(%eip)表示将要执行的下一条指令在存储器中的地址。 C中的聚集数据类型,例如数组和结构,在汇编代码中是用连续的字节表示的。即使是对标量数据类型,汇编代码也不区分有符号或者无符号整数,不区分各种类型的指针,甚至于不区分指针和整数。

gcc -O2 -S code.c

 加上-S就能看到C编译器产生的汇编代码。

pushl %ebp

表示把寄存器ebp中的内容压入程序栈中。

机器实际执行的程序只是对一系列指令进行编码的字节序列,机器动产生这些指令的源代码几乎一无所知。

要查看目标代码文件的内容,反汇编器(disassembler)的价值无法估量。

在unix系统中,用objdump可以启动反汇编器。

IA32指令长度从1~15个字节不等。

指令格式是按照这样一种方式设计的,从某个给定位置开始,可以将字节唯一地译码成机器指令。例如pushl %ebp 是以字节值55开头的。

编译器有时会在代码中插入一条nop指令,是为了填充存储该过程的空间。

Intel术语“字”表示16位数据类型。 在过程(Procedures)处理中,对前三个寄存器(%eax,%ecx,%edx)的保存和恢复惯例将不同于接下来的三个(%ebx,%edi,%esi)。最后两个寄存器(%ebp,%esp)保存着只想程序栈中重要位置的指针,只有根据栈管理的标准惯例才能修改这两个寄存器中的值。

指令的操作数分为三类:

一、立即数($123,$-134),由一个$符号加上数字,数字可以是十六进制。

二、寄存器(%eax,%ebx)。

三、存储器,根据计算出来的存储器地址访问某个存储器位置。

一般寻址格式:Imm(E(b),E(i),s)其中Imm是立即数偏移,E(b)表示一个基址寄存器,E(i)表示变址寄存器或者叫索引寄存器,s是伸缩因子,只能取值1、2、4、8. 计算方法:Imm+E(b)+E(i)xS

IA32对于mov指令的要求是不允许两个操作数都是存储器位置。

movsbl、movzbl的区别:    

1
2
3
4
5
6
%dh = 8D     
%eax = 98765432     
movb %dh,%al    //%eax = 9876548D     
movsbl %dh,%eax    //%eax = FFFFFF8D     
movzbl %dh,%eax    //%eax = 0000008D movzbl用0填充eax的高24位;
movsbl用dh中的最高位填充eax的高24位。

至于为何是填充eax的高24位,原因是movsbl、movzbl的源操作数是字节长度(就是这里的dh),而这条指令会执行扩展32位,就是把目的操作数的高24位进行相应的扩展操作。

根据惯例,所有返回整数或指针值的函数都是通过将结果放在寄存器%eax中来打到目的的。

LEA指令是将有效地址计算出来放到目的操作数中而不是取出结果放入,虽然它看起来和mov的感觉差不多:) 在for循环中初始化一个变量比如int i = 0..汇编中一般是这样做的:xorl %edx,%edx因为变量与自身做异或总是得到0的。

乘除法相比较而言是比较高级的操作,一般乘法是把一个数放入%eax,再与另一个数相乘,如:imull 12(%ebp),此处假设另外一个乘数在%ebp偏移12的地方,结果分别放在%eax和%edx中,分别保存了结果的低32位和高32位,最好根据实际情况(机器大端、小端)压栈。

除法类似,只是结果中的商保存在%eax中,余数放在%edx中。

Comments