3.1历史观点
Intel处理器系列:俗称x86,开始时是第一代单芯片、16位微处理器之一。
第一代是8086,也是汇编课程中学习的处理器型号。
每个后继处理器的设计都是后向兼容的,可以保证较早版本上编译的代码在较新的处理器上运行。
3.2程序编码
gcc -01 -o p p1.c
-01 表示使用第一级优化。优化的级别与编译时间和最终产生代码的形式都有关系,一般认为第二级优化-02 是较好的选择。
-o 表示将p1.c编译后的可执行文件命名为p
GCC将源代码转化为可执行代码的步骤:
C预处理器——扩展源代码-生成.i文件
编译器——产生两个源代码的汇编代码-——生成.s文件
汇编器——将汇编代码转化成二进制目标代码——生成.o文件
链接器——产生可执行代码文件
3.2.1机器级代码
1.机器级编程的两种重要抽象
(1)指令集结构ISA
(2)机器级程序使用的存储器地址是虚拟地址。
2.处理器:
一条机器指令只执行一个非常基本的操作。
3.2.2代码示例
int accum = 0;
int sum(int x, int y)
{
int t = x + y;
accum += t;
return t;
}
在命令行上使用 “-s”选项,就能得到c语言编译器产生的汇编代码:
gcc -01 -S code.c 产生一个汇编文件 code.s
如果使用“-c”命令行选项,GCC会编译并汇编该代码:
gcc -01 -c code.c
反汇编器的使用:
objdump -d xxx.xx
3.3数据格式
8 位:字节
16位:字
32位:双字
64位:四字
char 字节 1字节 short 字 2字节 int 双字 4字节 long int 双字 4字节 long long int (不支持) 4字节 char * 双字 4字节 float 单精度 4字节 double 双精度 8字节 long double 扩展精度 10/12字节
3.4访问信息
3.4.1操作数指示符
操作数:指示出执行一个操作中要引用的源数据值,以及放置结果的目标位置。
存储器中
3.4.2数据传送指令
(1)功能
把一个字节(字)操作数从源SRC传送至目的地DST
(2)格式
MOV DST,SRC
(1)压栈push
功能:把数据压入到栈上
(2)出栈pop
功能:弹出数据
都只有一个操作数。
3.4.3数据传送示例
1.c操作符*执行指针的间接引用。
2.c语言中的指针其实就是地址,间接引用指针就是将该指针放在一个寄存器中,然后在存储器引用中使用这个寄存器
3.局部变量通常保存在寄存器中,而不是存储器
3.5算数和逻辑操作
3.5.1加载有效地址
1.加载有效地址指令——leal,是movl指令的变形
2.目的操作数必须是一个寄存器。
3.5.2一元操作和二元操作
一元操作:只有一个操作数,既是源又是目的,可以是一个寄存器,或者存储器位置。
二元操作:源操作数 目的操作数
注意:第一个操作数可以是立即数、寄存器或者存储器位置
第二个操作数可以是寄存器或者存储器位置
但是不能同时是存储器位置。
3.5.3移位操作
SAL 算术左移
SHL 逻辑左移
SAR 算术右移(补符号位)
SHR 逻辑右移(补0)
源操作数:移位量——立即数或CL
目的操作数:要移位的数值——寄存器或存储器
3.5.5特殊的算术操作
(1)无符号数乘法:mull
(2)补码乘法:imull
3.6控制
jump指令:可以改变一组机器代码指令的执行顺序。
3.6.1条件码
CF:进位标志
ZF:零标志
SF:符号标志
OF:溢出标志
3.6.2访问条件码
这个指的是SET指令,通过set与不同的条件码的组合,达到不同的跳转条件。
3.6.3跳转指令及其编码
跳转指令有几种不同的编码,最常用的是PC(程序计数器)相关的。
需要注意的是,jump分为直接跳转和间接跳转:
直接跳转:后面跟标号作为跳转目标 间接跳转:*后面跟一个操作数指示符
当执行与PC相关的寻址时,程序计数器的值是跳转指令后面的那条指令的地址,而不是跳转指令本身的地址。
3.6.4翻译条件分支
条件表达式和语句从c语言翻译成机器语言,最常用的方式就是结合有条件和无条件跳转。
3.6.5循环
通用形式:
do
body-statement
while(test-expr);
注意:循环至少循环一次。
2.while循环
通用形式:
while (test-expr)
body-statement
GCC的方法是,使用条件分支,表示省略循环体的第一次执行:
if(! test-expr)
goto done;
do
body-statement
while(test-expr);
done:
接下来:
t = test-expr;
if(!t)
goto done:
loop:
body-statement
t = test-expr;
if(t)
goto loop;
done:
归根究底,还是要把循环改成do-while的样子,然后用goto翻译。
3.for循环
for循环可以轻易的改成while循环,所以再依照上面的方法改成do-while再翻译即可。
3.6.6条件传送指令
1.控制的条件转移:条件操作的传统方法。
2.数据的条件转移:先计算一个条件操作的两种结果,然后再根据条件是否满足从中选取一个。
3.6.7switch语句
Switch语句是多重分支的典型,而且使用的是跳转表这种数据类型,是的搜索的更快更高效。
所以这里的关键就是要领会使用跳转表是一种非常有效的实现多重分支的方法。
3.7过程
3.7.1栈帧结构
栈用来传递参数、存储返回信息、保存寄存器,以及本地存储。
为单个过程分配的那部分栈帧。
最顶端的栈帧以两个指针界定:
寄存器%ebp-帧指针
寄存器%esp-栈指针
栈指针可移动,所以信息访问多相对于帧指针。
3.7.2转移过程
(1)和转移指令相似,同样分直接和间接,直接调用的目标是标号,间接调用的目标是*后面跟一个操作数指示符,和JMP一样。
(2)指令的效果是将返回地址入栈,并跳转到被调用过程的起始处。返回地址是还在程序中紧跟在call后面的那条指令的地址,然后就会用到ret了。
ret指从栈中弹出地址,并跳转到这个位置。
这个指令可以使栈做好返回的准备,等价于:
movl %ebp,%esp
popl %ebp
3.7.3寄存器使用惯例
1.程序寄存器组是唯一能被所有过程共享的资源。
2.这个惯例是为了防止一个过程P调用另一个过程Q时寄存器中的值被覆盖。
作业:
运行结果:
去掉.开头的句子之后: