cs107 编程范式(七)

399 阅读4分钟

紧接着上一节课的内容,本次课程来描述一下软件管理器如何对堆进行操作。

1.PNG

malloc函数每次会从堆区域中申请所需字节空间,并且记录下申请空间的大小。记录下的空间大小用于后续free函数的释放。

void* arr = malloc(40 * sizeof(int));

从直觉上将,这段代码返回的是160字节大小的空间,但是实际上使用的空间大小是超过160字节的,因为在分配空间的过程中,会记录一定的信息,比如当前空间的大小。如下图所示:

2.PNG

如果你执行如下所示的代码:

int *arr = malloc(40 * sizeof(int));
free(arr + 60);

就会导致问题,因为free一般不会为你做错误检查,其默认从当前指针所指位置向前读取空间大小的信息。

不同的malloc会使用不同的启发式策略来进行内存的分配

比如,将堆区域的内存按照2的次幂形式进行组织

3.PNG

比如需要7字节,232^3中分配8字节的空间。这样每次你可能会获得比所需要空间更大的空间,但是这样会更加便利和迅速。

堆区域中内存的组织形式

4.PNG

堆区域中的空闲空间通过链表来进行组织的,每个空闲块头部会存档一个指针指向下一个区域,内存管理器通常会通过遍历的方式来寻找空闲空间。通常有首次匹配原则,最佳匹配原则,下次适配。

对于free的区域来说,我们只需要将其加入链表即可,如下图所示

5.PNG

现在我们来看另一种情况,如果内存的情况如下图所示,当我们需要100字节大小的内存空间的时候,当前的堆内存区域是无法满足我们的需要。虽然当前的空闲区域总共有160字节大小,但是整个空闲块是分散了,没有任何一块是符合我们的需要。

6.PNG

因此一种自然的想法是如下所示:

7.PNG

我们将已经使用的内存块聚集在一起,放在堆区域开始的位置,这样就有足够的空间供我们使用。

但是,这又会导致一个问题。用户所持有的指针现在指向的位置,不在是之前指向的内存块了。在视频中,老师说目前主流的系统都没有采用这种方式进行处理,只有很早之前的mac系统采用这种方式。我们来看一下,他们是如何进行处理的。

8.PNG

首先,用户所持有的的指针实际上指向的是一个句柄结构,这个句柄结构中存在很多的信息,其中就有指向堆区域中分配内存块的首地址。在这种情况下,如果对堆内存进行压缩,影响的只有句柄结构,不再会对用户所持有的指针产生影响了。但是这也会存在新的问题,因为早期的mac系统后台会使用一个低优先级的线程来处理堆区域压缩,如果需要强制压缩,就会提升线程的优先级。这里用户取地址操作和堆内存压缩是不能同时发生的。

在操作堆内存的时候,我们需要如下操作

void **handle = NewHandle(40);
HandleLock(handle);
....

HandleUnlock(handle);

接下来,我们来看一下栈区域

栈区

9.PNG

栈区域所使用的空间和活动函数的数量成正比例。

void A() {
    int a;
    short b[2];
    double c;
    B();
    C();
}

void B() {
    int d;
}

void C() {
    double e;
}

当我们执行如上所示的代码时,栈区域会为函数中的变量分配空间,同时栈指针也会随着移动。

10.PNG

此外,栈指针只能访问当前函数区域中变量。

11.PNG

汇编部分

12.PNG

我们编写的代码首先会编译成汇编代码,然后再由汇编代码翻译成机器指令,而代码段存储的就是机器指令。

在正式开始汇编语言介绍前,我们先看看指令是如何执行的。

13.PNG

任何计算机的运算都是由算术逻辑单元ALU来执行的,而ALU中的数据是来自于寄存器,寄存器中的数据来自于内存。寄存器相当于ALU和内存之间的缓存。在本课程中,我们假设寄存器是32个通用寄存器(R1, R2, ...)。

举个例子

但我们执行j += 1;操作的时候,先将j中的数据 load 到R1中,然后将1 load 到R2中,ALU进行计算并将结果放入R3中,R3再将结果存入内存中的j中。

这节课就先到这里了,具体内容下节课在进行讲述。