3.7 过程
文章目录
3.7 过程
3.7.1 运行时栈
- x86-64的栈向低地址方向增长,栈指针%rsp指向栈顶元素。减小栈指针分配空间,增加栈指针释放空间
- 当x86-64过程需要的存储空间超出寄存器存放大小,就会在栈上分配空间(栈帧)
- 许多函数不需要栈帧(所有局部变量都保存在寄存器,不调用其他函数)(树结构中的叶子过程)
3.7.2 转移控制
P->Q:call push A(P指令后的地址),PC设为Q;ret pop A,把PC设为A
指令 | 描述 |
---|---|
call Label | 过程调用 |
call *Operand | 过程调用 |
ret | 从过程调用中返回 |
3.7.3 数据传送
x86-64中,寄存器最多传递6个整型参数,超过部分栈传递
操作数大小(位) | 参数数量1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
64 | %rdi | %rsi | %rdx | %rcx | %r8 | %r9 |
32 | %edi | %esi | %edx | %ecx | %r8d | %r9d |
16 | %di | %si | %dx | %cx | %r8w | %r9w |
8 | %dil | %sil | %dl | %cl | %r8b | %r9b |
3.7.4 栈上的局部存储
- 局部数据必须存放在内存的情况
- 寄存器不够存放所有本地数据
- 对局部变量使用地址运算符‘&’,因此必须能够为它产生一个地址
- 某些局部变量是数组或结构,因此必须能够通过数组或结构引用被访问到
3.7.5 寄存器中的局部存储空间
- 寄存器组是唯一被所有过程共享的资源
- 被调用者保存寄存器:%rbx、%rbp和%r12~%r15
- 调用者保存寄存器:除了栈指针%rsp
3.7.6 递归过程
3.8 数组分配和访问
3.8.1 基本原则
T A[N]
- L:T的大小(字节)
- A:数组开头指针,值为$x_A$
- 元素i:存放在地址为$x_A+L·i$的地方
3.8.2 指针运算
- &:指针
- *:间接引用指针(值)
- Expr与- &Expr等价
3.8.3 嵌套的数组
- 数组元素在内存中按照“行优先”的顺序排列
- T D[R][C]
- D[i][j]的内存地址:&D[i][j] = $x_D$+L(C·i+j)
- L:数据类型T大小(字节)
3.8.4 定长数组
3.8.5 变长数组
3.9 异质的数据结构
3.9.1 结构
3.9.2 联合
3.9.3 数据对齐
3.10 在机器级程序中将控制与数据结合起来
3.10.1 理解指针
- 每个指针都对应一个类型。例:
int *ip
:int类型指针char **cpp
:char*类型指针
- 每个指针都有一个值(类型对象的地址)。NULL(0)值表示没指向任何地方
- 指针用
&
运算符创建- lvalue:可以出现在赋值语句左边的表达式
- 这个运算符可以应用到lvalue类的C表达式上
- 机器代码常常用leaq指令(计算内存引用地址)实现
*
操作符用于间接引用指针- 结果是一个值
- 用内存引用实现:要么是存储到指定地址,要么是从指定地址读取
- 数组与指针紧密联系
- 数组名字可以像指针变量一样引用但不能修改
- 数组引用(
a[3]
)与指针运算和间接引用(*(a + 3)
)有一样的效果- 数组引用和指针运算都需要用对象大小对偏移量进行伸缩
- 当我们写表达式p + i,这里指针p的值为$p$,得到的地址计算为$p+L·i$,$L$是与p相关联的数据类型的大小
- 指针也可以指向函数(值是该函数机器代码表示中第一条指令的地址)
- 定义函数:
int fun(int x,int *p);
- 声明指针并赋值:
1 2
int (*fp)(int,int *);//括号是必须的,防止被解读成(int *) fp(int,int *); fp = fun;
- 调用:
1 2
int y = 1; int result = fp(3,&y);
- 定义函数:
3.10.2 应用:使用GDB调试器
3.10.3 内存越界引用和缓冲区溢出
3.10.4 对抗缓冲区溢出攻击
- 栈随机化
- 栈破坏检测
- 限制可执行代码区域