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个整型参数,超过部分栈传递

操作数大小(位)参数数量123456
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相关联的数据类型的大小
  • 指针也可以指向函数(值是该函数机器代码表示中第一条指令的地址)
    1. 定义函数:
      int fun(int x,int *p);
    2. 声明指针并赋值:
      1
      2
      
      int (*fp)(int,int *);//括号是必须的,防止被解读成(int *) fp(int,int *);
      fp = fun;
      
    3. 调用:
      1
      2
      
      int y = 1;
      int result = fp(3,&y);
      

3.10.2 应用:使用GDB调试器

3.10.3 内存越界引用和缓冲区溢出

3.10.4 对抗缓冲区溢出攻击

  1. 栈随机化
  2. 栈破坏检测
  3. 限制可执行代码区域

3.10.5 支持变长栈帧

3.11 浮点代码