第一章计算机系统漫游

第一部分 程序结构与执行

第二章 信息的表示与处理

二进制 十进制 十六进制互转

低转高:用短除法反复短除高进制取余

高转低:用高进制的幂乘以每个高进制数字

字数据大小

每台计算机都有字长,指明指针数据的标称大小(normal size),虚拟地址以字来编码,对于$\omega$位的机器虚拟地址范围为:0~$2^{\omega}-1$,程序最多访问$2^{\omega}$个字节

32位机器虚拟地址限长为$2^{32}$字节=$2^{32}/2^{30}$=$2^{2}$=4Gb,32位操作系统最多只能使用4Gb的内存

寻址和字节顺序

小端法 最低有效字节在最前面

大端法 最高有效字节在最前面

整数表示

原码,补码,反码

机器数:一个数在计算机中的二进制表示形式, 叫做这个数的机器数。机器数是带符号的,在计算机用一个数的最高位存放符号, 正数为0, 负数为1。

真值:因为第一位是符号位,所以机器数的形式值就不等于真正的数值。例如上面的有符号数 10000011,其最高位1代表负,其真正数值是 -3 而不是形式值131(10000011转换成十进制等于131)。所以,为区别起见,将带符号位的机器数对应的真正数值称为机器数的真值。

原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位二进制:

[+1]原 = 0000 0001

[-1]原 = 1000 0001


反码的表示方法是: 正数的反码是其本身 负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.

[+1] = [00000001]原 = [00000001]反

[-1] = [10000001]原 = [11111110]反


补码的表示方法是: 正数的补码就是其本身 负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)

[+1] = [00000001]原 = [00000001]反 = [00000001]补

[-1] = [10000001]原 = [11111110]反 = [11111111]补

对于负数, 补码表示方式也是人脑无法直观看出其数值的. 通常也需要转换成原码在计算其数值.


有符号数转无符号数公式

$$ T2U_{\omega}(x)= \begin{cases} x+2^{\omega}&{x<0} \\ x&x\ge0 \end{cases} $$

无符号数转有符号数公式

$$ U2T_{\omega}(u)= \begin{cases} u&u\le Tmax_{\omega} \\ u-2^{u}&u>Tmax_{\omega} \end{cases} $$

浮点数的表示

第三章 程序的机器级表示(汇编)

数据格式

Intel采用术语”字(word)“表示16位数据类型,因此32位数称为”双字(double word)“,64位数为”四字(quad words)“


x86_64中C语言数据类型大小

C声明Intel数据类型汇编代码后缀大小(字节)
char字节b1
shortw2
int双字l4
long四字q8
char*四字q8
float单精度s4
double双精度l8

数据传送指令

movb:传送字节 movw:传送字 movl:传送双字 movq:传送四字

x86_64寄存器图

操作数指示符

立即数:用来表示常数,在ATT格式汇编,立即数的书写方法为”$“后面跟着一个用标准C表示法的整数。不同的指令允许的立即数范围也不同,汇编器会自动选择最紧凑的方式进行编码

寄存器:它表示某个寄存器的内容,16个寄存器的低位1字节,2字节,4字节或8字节中的一个作为操作数,这些字节数分别对应于8位,16位,32位或64位,我们用符号$r_a$表示任意寄存器,用$R[r_a]$来表示他的值,这是将寄存器集合看成一个数组R,用寄存器标识符做索引

内存引用:它会根据计算出的地址访问某个内存位置,因为将内存看成一个很大的字节数组,用符号$M_b[Addr]$表示对存储在内存中从地址$Addr$开始的b个字节的引用。

操作数寻址表

$M$:主存寻址操作 $R$:寄存器寻址操作 $r$:为寄存器 $r_b$:为偏移量 $Imm$:为基地址

类型格式操作数值名称
立即数${$Imm}$$Imm$立即数寻址
寄存器$r_a$$R[r_a]$寄存器寻址
存储器$Imm$$M[Imm]$绝对寻址
存储器$(r_a)$$M[R(r_a)]$间接寻址
存储器$Imm(r_b)$$M[Imm+R[r_b]]$(基地址+偏移量)寻址
存储器$(r_b,r_i)$$M[R[r_b]+R[r_i]]$变址寻址
存储器$Imm(r_b,r_i)$$M[Imm+R[r_b]+R[r_i]]$变址寻址
存储器$(,r_i,s)$$M[R[r_i]\cdot s]$比例变址寻址
存储器$Imm(,r_i,s)$$M[Imm+R[r_i]\cdot s]$比例变址寻址
存储器$(r_b,r_i,s)$$M[R[r_b]+R[r_i]\cdot s]$比例变址寻址
存储器$Imm(r_b,r_i,s)$$M[Imm+R[r_b]+R[r_i]\cdot s]$比例变址寻址

压入和弹出栈数据

在x86-64,程序栈放在内存的某个区域中,栈向下增长,这样一来栈顶元素的地址是所有栈中元素最低的。

指令效果描述
pushq S$R[\%rsp]\leftarrow {R[\%rsp]-8};\\ M[R[\%rsp]]\leftarrow S$四字压栈
popq D$D\leftarrow M[R[\%rsp]];\\ R[\%rsp]\leftarrow R[\%rsp]-8$四字出栈

指令pushq %rdp等效于以下指令

1
2
subq $8,%rsp
movq %rbp,(%rsp)

指令popq %rax等效以下指令

1
2
movq (%rsp),%rax
addq $8,%rsp

加载有效地址

加载有效地址的指令形式从内存读数据到寄存器,但实际上根本没有引用内存,他的第一个操作数看上去是一个内存引用,但该只看并不是从指定位置读入数据,而是将计算出有效地址写入到目的操作数,mov传送的是地址内容,lea传送的是地址

使用lea+mov实现C语言指针

1
2
3
4
void point(){
    int a=8;
    int *p=&a;
}
1
2
3
4
5
6
7
8
9
point():
        pushq   %rbp
        movq    %rsp, %rbp
        movl    $8, -12(%rbp)
        leaq    -12(%rbp), %rax
        movq    %rax, -8(%rbp)
        nop
        popq    %rbp
        ret

5-6行,lea操作将-12(%rbp)(即a的内存地址)放入rax寄存器,然后将rax寄存器内存储的地址放到内存中a的地址偏移4字节处

一元和二元操作

指令描述
add加法操作
sub减法操作(目的操作数-原操作数)
imul有符号乘法操作
mul无符号乘法操作
idiv有符号除法
div无符号除法
inc加1操作
dec减1操作
clto转换8字

控制

条件码

过程

运行时栈

C语言过程调用机制的一个关键特征(大多数其他语言也是如此)在于使用了栈数据结构提供的后进先出的内存管理机制. x86_64的栈地址是向低地址方向增长,而栈指针%rsp指向栈顶元素。通过push指令和pop指令将数据存入栈中或是从栈中取出。将栈指针减小一个适当的量可以为没有指定初始值的数据在栈上分配空间,类似通过增加栈指针来释放空间

当x86_64过程需要的存储空间超过寄存器能存放的大小时,就会在栈上分配空间,这个部分称为过程的栈帧

数组的分配与访问

异数的数据结构

第二部分 在系统上运行程序

第三部分 程序间的交流与通信