虽然可以使用优化编译器,但对于严谨的程序员来说,能够阅读和理解汇编代码仍然是一项很重要的技能。
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 |
|
至于为何是填充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中。