machine basics
在内存中不存在聚合类型(数组和结构),只有连续分配的字节
X86-整数寄存器
数据格式
Intel用术语字 “word”表示116位的数据类型,32位数据被称为双字,64位数据被称为四字 标准的int被存储为4个字节、32位、双字,指针存储为8个字节4字
C声明 | Intel数据类型 | 汇编代码后缀 | 字节 |
---|
char | 字节 | b | 1 | short | 字 | w | 2 | int | 双字 | l | 4 | long | 四字 | q | 8 | char* | 四字 | q | 8 | float | 单精度 | s | 4 |
?
寄存器
可以存放1、2、4 或 8 个字节的“整数”数据 对于64位的系统来说,地址对应着8个字节(无类型指针)
汇编操作
数据传输指令
在内存和寄存器之间传输数据:
操作数类型:
- 立即数:常量 $0x400 $-533 需要以美元符号开头,占用1、2、4个字节
- 寄存器:16个寄存器之一 %rsp保留作为指针
- 内存:寄存器给定地址处的 **8 **个连续字节的内存 (%rax) 其他寻址模式
注意事项:
- 数据传输指令有4个变种:movb传输单字节、movw传送字、movl传送双字、movq传送四字
- 数据传输指令用来传输立即数的时候,目的寄存器的大小必须和指令的字符指定的大小相同,一般而言mov只会更新目的操作数子划定的寄存器字节或者内存位置,但是movl在以寄存器作为目的时,会将高位4字节也置为0.
- 常规的movq只能以表示为32位补码数字的立即数作为源操作数,然后将这个值进行符号扩展到64位再放进目的位置。movabsq指令能够以任意64位立即数值作为源操作数,并且只能以寄存器作为目的。
- 数据传输指令还存在零拓展和符号扩展两种变形 P123
寻址模式
直接寻址
对应着指针引用,把寄存器和内存理解成分开的两部分,寄存器存在于CPU中
完整的寻址模式
计算地址指令?
栈
程序栈放在内存中的某个区域,栈向下增长,栈顶元素的地址是所有栈中元素地址中最低的,栈指针rsp保存着栈顶元素的地址
算术和逻辑操作
详情参见P129图3.10
machine control
条件码
CMP根据两个操作数的差来设置条件码 Test 的行为与AND相同,但是不更新寄存器的值,只设置条件码。典型的操作是两个操作数相同,来判断是正数、负数还是0。 操作数为0就会置为0 访问条件码的方法 通过条件码某种组合将一个字节设为0或1 通常是一个寄存器的地位单字节寄存器或者一个单字节内存,为了得到一个32位或者64位的结果需要对高位清零(用数据传输0扩展) P137图3.14
comp:
cmpq %rsi, %rdi
setl %al
movzbl %al, %eax
ret
跳转指令
jmp指令是无条件跳转.他可以是无条件跳转,后面直接跟标号,也可以是间接跳转,跳转目标从寄存器或者内存位置中读出,写法" *** **"是后面跟一个操作数指示符 P139 ?
条件语句
使用控制的条件传输
核心是写出go风格的代码,当条件满足时沿着一条执行路径执行,当条件不满足时,就走另外一条路径
if(!test)
goto false
then-statement
goto done
fales:
else_statement
done:
使用数据的条件转移
计算一个条件操作的两结果,然后根据是否满足条件从中选取一个,只有在一些受限的情况下可以使用 因为现代处理器使用流水线来获得高性能,所以会进行分支预测,这种方法可以降低分支预测错误的惩罚
循环
do-while循环
loop:
body-statement
t=test-expr
if(t)
goto loop
while循环
第一种形式
goto test;
loop:
body-statement
test:
t-test-exper
if(t)
goto loop
第二种:guraded-do 转化成do-while的形式
t=test-expr
if(!t)
goto done
loop:
body-statement
t=test-expr
if(t)
goto loop
done:
for循环
for循环的本质是一个需要对条件状态进行更新的while循环,需要维护 init-test test-expr update-test 一般而言index是通过%rdx进行维护,看到add $1 %rdx 就可以考虑是不是for循环 因此for循环也可以转化成基于while 和基于do-while两种形式的代码
init-expr
while(test-expr){
body-statement
update-exper
}
但是也存在特例 P159 eg 3.29 (代码中的continue可能会阻塞对index的更新,我们需要做的就是把对于index的更新放到一个单独的程序块中,然后使用goto代替continue)
switch语句
根据一个索引值进行多重分支,通过跳转表和index的配合实现 跳转表单独存放,使用 jmp *.L4(,%rsi,8)完成跳转查询 P161 注意要点:
- 对于默认情况的处理
- 对于不存在的标号的处理是跳转到默认情况
- 对于结尾没有break的语句,对应的汇编代码中不存在跳转到结尾的语句
?
machine-procedures
过程调用(P调用Q,Q执行结束后返回P)需要包括以下几个步骤
- 传递控制:在进入过程Q的时候,PC必须设置为Q的代码起始位置,在过程结束返回时,要把PC设置位P调用Q后的下一条指令的位置
- 传递数据:P必须为Q提供一个或者多个参数,同时Q需要向P完成一个返回值
- 分配和释放内存空间:在开始时,Q可能需要为局部变量分配空间,在返回前,又必须释放掉这些空间
?
运行时栈
x86的栈向低地址方向增长,栈指针%rsp指向栈顶元素,可以用pop和push指令将数据存入或者取出 当P调用Q时,控制和数据信息就会被添加到栈尾,当P返回时在释放掉。 当过程P 调用Q时,会把返回地址压入栈,时P相关的状态,Q的代码会把它当作当前栈的边界。通过寄存器最多可以传递6个整数值,更多的话就需要使用栈进行传递。如果有一个函数的参数可以使用寄存器传递并且是叶子过程,那么久不需要使用栈帧。
转移控制
call Q指令会把地址A(返回地址,紧跟在call指令后面的那一条指令)压入栈,同时把PC设置为Q的起始地址。对应的指令ret会从栈中pop A,并且将返回地址设置为A
数据传送
通过寄存器传递参数 P168 如果参数数量大于6,要对7~n的参数设置栈空间
栈上的局部存储
寄存器中的局部存储空间
被调用者保存寄存器 调用者保存寄存器:调用者需要自己提前在栈中保存防止被修改
递归调用
每一次函数调用都有自己的私有的状态信息存储空间 P175
long rfact(long n){
long result;
if(n<=1)
result=1;
else
result=n*rfact(n-1);
return result;
}
rfact:
pushq %rdx
movq %rdi, %rdx
movl $1, %eax
cmp $1,%rdi
jle .L35
leaq -1(%rdi),%rdi
call rfact
imulq %rbx,%rax
.L35:
popq %rdx
ret
|