汇编语言

可理解

堆结构

堆块

堆块是堆的基本组织单位,包括块首和块身;一般位于整个堆区的开始位置,用于索引堆区中所有堆块的重要信息;堆块共分为占有态(系统管理)和空闲态(程序员管理),对于空闲态堆块而言,块首会额外存储两个4字节指针:Flink(前向)指针和Blink(后向)指针。

​ 当我们使用函数申请的地址指针/句柄,会指向堆块块身的首地址(直接越过8字节的块首);堆块的大小包括块首,当申请16字节时,实际会分配24字节(自动加首部);堆块的单位是8字节,因此不足会按8字节进行分配。

堆表

​ 在windouws系统中占有态的堆块被使用程序索引,空闲态的对快被堆表索引;其中最重要的堆表有:空闲双向链表(空表,freelist)和快速单向链表(快表,lookaside,加速堆块分配);空表包含空表索引(也叫空表表头,大小为128的数组(free[]),数组的每一项包含两个指针用于标识一条空表)和空闲链块两部分。

空表索引的free[1]用于白哦是堆中所有大小为8字节的空闲堆块,之后每个索引项指引的空闲堆块递增8字节。free[0]用于存放≥1024B而小于512KB的堆块并升序排列;因此在分配时会先从free[0]中反向查找,满足时再正向搜索最小能满足要求的空闲堆块。

堆块的释放和分配操作可能引发堆块合并,即当堆管理系统发现两个空闲堆块相临时会进行堆块合并操作。

寄存器

中央处理器CPU的组成部分,因CPU的运算速度远远高于内存的读写速度,为避免被拖慢,CPU会自带一级缓存和二级缓存;对于读写频繁的数据都会存档在寄存器中,CPU优先读取寄存器,再由寄存器跟内存交换数据。32位CPU指的就是寄存器的大小。

变址寄存器(通用寄存器ESI【源地址指针】,EDI【目的地址指针】 指针寄存器EBP,ESP)用于栈操作中存放操作数和计算操作数的有效地址,不可分割为8位寄存器。

[!CAUTION]

CPU使用寄存器读数据比从一/二级缓存里更快

需掌握

内存区域

  • 代码区:存储被装入执行的二进制机器代码,可执行文件包含的二进制机器代码将被加载到内存的代码区,由处理器依次取出送去ALU执行;
  • 静态数据区:存放程序运行的全局变量、静态变量等;
  • 堆区:用于动态分配进程内存;需要使用专有函数进行申请,其大小受限于计算机的虚拟内存;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*C使用malloc/calloc作为堆分配关键字,int**意为强制类型转换*/
int **a = (int**)malloc(sizeof(int*)*10); //**a:指向指针的指针,存的是int*指针类型的地址
free a; //释放a指向的堆内存,当未指向额外堆内存时
a = NULL; //避免野指针
/*否则先释放内层,再释放外层,避免内存泄露*/
for (i = 0; i < 10; i++) {
a[i] = (int*)malloc(sizeof(int)*5);
if (a[i] == NULL) {
// 若某个内层分配失败,需先释放已分配的内层内存,再释放外层
while (--i >= 0) {
free(a[i]);
a[i] = NULL;
}
free(a);
a = NULL;
return 1;
}
/*C++中使用new作为动态分配关键字*/
int *p1 = new int[200];
delete[] p1; //手动释放堆区内存
p1 = nullptr; //避免野指针
  • 栈区:动态存储函数之间的调用关系;向低地址扩展的数据结构,采取FILO的访存方式,主要存储函数运行时的局部变量、数组等。

    [!TIP]

    在栈区中,变量a先入栈,变量b后入栈,则变量a所在内存地址比变量b高

    对代码块int *p1 = new int[200];char *p2 = new char[30];我们可以分析得到,其所申请的内存将分配到堆区,而p1和p2的值的大小无法确定(堆分配不是栈那样的顺序地址)

堆区和栈区的区别(4个区别)

  1. 申请方式上,栈由程序自动分配,而堆需要程序员自己申请(如C语言的malloc函数);

  2. 申请效率上,栈由于自动分配,速率较快,不产生内存碎片,而堆因手动分配速度较慢,可能产生内存碎皮;

  3. 释放方式上,栈在作用域结束后自动释放而堆需要作用域结束前手动释放,否则会造成内存泄露;

  4. 使用场合上,若需要的内存很少且运行前可知需多少内存时,使用栈,否则使用堆。

1
2
3
4
/*区分*/
int a;
int *p = &a; //若p是局部变量则在栈区,a是直接声明的,所以也在栈区
int *p = new int; //指针p本身在栈区但指向的内存是new分配的,在堆区

数据寄存器

  • 32位CPU有4个32位通用寄存器EAX、EBX、ECX和EDX。对其低16位的存取不会影响到高16位的数据。这些低16位寄存器分别命名为AX、BX、CX和DX与先前CPU中的寄存器相一致;

  • 4个16位寄存器又可分割为8个独立的八位寄存器,每个寄存器有自己的名称,可独立存取

    AX:AH-AL BX:BH-BL CX:CH-CL DX:DH-DL

  • EAX(累加器):使用频率高,用于算术运算和输入/输出操作,还常用于存储函数返回值;

  • EBX(基地址寄存器):作为内存指针使用,用于访存;

  • ECX(计数寄存器):用于控制循环次数和;在移位操作时,用CL来指明移位的位数;

  • EDX(数据寄存器):进行乘除运算时作为默认数参与运算,也用于存放I/O设备端口。

函数栈帧(!important)

ESP和EBP之间的内存空间为当前栈帧,EBP标识了当前栈帧的底部,ESP标识当前栈帧的顶部;对于当前正在运行的函数栈帧总是在栈顶

指令指针寄存器

IP,永远指向当前等待执行的指令;EIP用于存储下次将要执行的指令在代码段的偏移量。

标志寄存器(EFlags)

在32位操作系统中大小为32位,可存32个标志。

  • ZF(零标志):运算结果为0,置1;
  • OF(溢出):按照无符号数计算加减法再转为有符号数,若超出有符号数表示范围(-128127,即1000000001111111)或运算结果错误,置1;
  • CF(进位):运算结果最高位产生进位/借位,置1;
  • SF(符号标志):运算结果最高位为1,置1。
EXAMPLE(8bit)
01111111+00000001=10000000(运算结果错误,本该为128,但是现在显示的-128) ZF=0 OF=1 CF=0 SF=1
00000000-10000000=10000000 (0-(-128)=128,超出范围) ZF=0 OF=1 CF=1 SF=1
10000000-01111111=00000001 ZF=0 OF=1 CF=0 SF=0
11111111+00000001=00000000 ZF=1 OF=0 CF=1 SF=0

x64架构CPU寄存器

寄存器均扩展至64位,名称的第一个字母E改为R,增加了8个64位通用寄存器R8-R15,也可使用其低位寄存器R8D(32位),R8W(16位),R8B(8位)。


第二章 结