本系列文章是本人读《深入理解计算机系统》时的摘抄和读书笔记,内容比较精简,可能会有错误,如想展开深入理解请阅读源书
序言
本章将介绍汇编语言和机器语言。
文章目录
3.7 过程
3. 7.1 运行时栈
当cpu需要的存储空间超出寄存器可存放的大小时,就会在栈上分配空间,这部分称为过程的栈帧。低地址为栈顶,高地址为栈底。
3.9 异质的数据结构
3.9.1 struct
创建一个数据类型,将可能不同类型的对象聚合到一个对象中,用名字来引用结构的各个组成部分。结构中所有组成部分都存放在内存中一段连续的区域内,指针指向结构的第一个字节的地址。
3.9.2 union
联合提供一种方式,规避掉c语言的类型系统,允许多种类型来引用一个对象,只用一块内存,大小为所有对象中最大的一个,因此在任何时候只能存储一个数据对象。联合中所有引用都指向数据结构的起始位置。
3.9.3 数据对齐
计算机系统要求某些类型的对象地址必须是某个K值,比如2、4、8(简化形成处理处理器和内存系统间接口的硬件设计)。
对于包含结构的代码,编译器可能会在字段之间插入间隙,以保证每个元素都满足这种对齐要求。举例:
struct A{
int a;
char b;
int c;
}
偏移量分别为0,4,8(而不是0,4,5,b和c之间插入一个3字节间隙)。此外编译器必须保证任何struct A*类型的指针都满足4字节对齐。
假如struct A中为int b; char c; 则会在对象c末尾填充3字节间隙。
3.10 在机器级程序中将控制与数据结合起来
3.10.1 指针
- int *p1:变量p1是一个指向int类型的指针
int **p2:变量p2是一个指向int指针的指针
T *p3:变量p3是一个指向泛型的指针
void *p4:变量p4是一个通用指针,可以指向任意类型(补充一个void* 指针的解释和应用)。 - 每个指针都有一个值,为指向对象的地址,NULL(0)表示该指针没有指向
- 将指针从一种类型强制转换成另一种类型,只是改变了它的类型(解码方式),不改变其值。
- 指针的大小是固定的,64位系统其指针大小是8字节。
- 指针也可以指向函数,比如:
int (*f)(int *); //表示指针f指向一个参数为(int *),返回类型是int的函数
int *f(int *); //相当于(int *) f(int *); //表示函数f返回类型是int*
- 常量指针和指针常量,举例:
const int* p;//常量指针,指向常量的指针,其指向对象不可更改
int* const p;//指针常量,指针内容为常量,也就是其指向不可更改
引申一下,指针和引用的区别(参考):
- 指针可以为空,引用不可为空(引用是对象的别名,那么初始化引用的前提是被引用的对象存在)。
- 引用不可以改变指向,但是可以改变初始化对象的内容,而指针即可以改变指向,又可以改变指向对象的内容。
- 引用的的大小是其指向的对象的大小,因为引用仅仅是一个别名;指针的大小与平台有关,在32位平台下指针大小为4个字节,64位平台是8字节。
- 引用比指针安全,引用不可能有空引用,指针有可能为空指针和野指针(指针指向内存被free掉)。
- 存在常量引用,不存在引用常量,因为引用的特质本身就是指向对象不可改变,所以常量引用可以是const int &r 或 int const &r。
- 指针和引用编译成汇编语言,其实现都是一样的。
- 指针传递:本质是值传递,也就是在栈中开辟新空间存放地址值,成为一个实参的副本,其改动不会改变实参的值。
引用传递:在栈中开辟新空间存放实参地址,其改动会改变实参的值。
3.10.3 内存越界引用和缓冲区溢出
缓冲区溢出:字符串长度超出了分配空间。
3.10.4 对抗缓冲区溢出攻击
- 栈随机化:栈的位置在程序每次运行时都有变化。
- 栈破坏检测:在栈帧中局部缓冲区与栈状态之间存储一个特殊的哨兵值,如果该哨兵值被某个函数的某个操作改变了,则程序异常中止。或者将哨兵值改为只读。
- 限制可执行代码区域。