当前位置:网站首页>The C Programming Language(第 2 版) 笔记 / 8 UNIX 系统接口 / 8.7 实例(存储分配程序)
The C Programming Language(第 2 版) 笔记 / 8 UNIX 系统接口 / 8.7 实例(存储分配程序)
2022-06-12 16:15:00 【M菜鸟M】
8.7 实例(存储分配程序)
我们在第 5 章给出了一个功能有限的面向栈的存储分配程序
本节将要编写的版本没有限制,可以以任意次序调用 malloc 和 freemalloc 在必要时调用操作系统以获取更多的存储空间
这些程序说明了通过一种与系统无关的方式编写与系统有关的代码时应考虑的问题,同时也展示了结构、联合和 typedef 的实际应用
malloc 并不是从一个在编译时就确定的固定大小的数组中分配存储空间,而是在需要时向操作系统申请空间
因为程序中的某些地方可能不通过 malloc 调用申请空间(也就是说,通过其它方式申请空间),所以,malloc 管理的空间不一定是连续的
这样,空闲存储空间以空闲块链表的方式组织,每个块包含一个长度、一个指向下一块的指针以及一个指向自身存储空间的指针
这些块按照存储地址的升序组织,最后一块(最高地址)指向第一块

当有申请请求时,malloc 将扫描空闲块链表,直到找到一个足够大的块为止,该算法称为首次适应(first fit)
与之相对的算法是最佳适应(best fit),它寻找满足条件的最小块
如果该块恰好与请求的大小相符合,则将它从链表中移走并返回给用户
如果该块太大,则将它分成两部分:大小合适的块返回给用户,剩下的部分留在空闲块链表中
如果找不到一个足够大的块,则向操作系统申请一个大块并加入到空闲块链表中
释放过程也是首先搜索空闲块链表,以找到可以插入被释放块的合适位置(将此次释放的空闲块插入到空闲块链表中)
如果与被释放块相邻的任一边是一个空闲块,则将这两个块合成一个更大的块,这样存储空间不会有太多的碎片
因为空闲块链表是以地址的递增顺序链接在一起的,所以很容易判断相邻的块是否空闲
我们在第 5 章中曾提出了这样的问题,即确保由 malloc 函数返回的存储空间满足将要保存的对象的对齐要求
虽然机器类型各异,但是,每个特定的机器都有一个最受限的类型:如果最受限的类型可以存储在某个特定的地址中,则其它所有的类型也可以存放在此地址中
在某些机器中,最受限的类型是 double 类型,而在另外一些机器中,最受限的类型是 int 或 long 类型
空闲块包含一个指向链表中下一个块的指针、一个块大小的记录和一个指向空闲空间本身的指针
位于块开始处的控制信息称为头部(header)
为了简化块的对齐,所有块的大小都必须是头部大小的整数倍,且头部已正确地对齐
这是通过一个联合实现的,该联合包含所需的头部结构以及一个对齐要求最受限的类型的实例
在下面这段程序中,我们假定 long 类型为最受限的类型:
typedef long Align; /* for alignment to long boundary */
union header {
// block header
struct {
// 指向空闲块链表中下一个块的起始位置
union header *ptr; /* next block if on free list */
/* * size 是整个块的大小,不是空闲部分的大小 * 单位是 header 的大小,不是字节(为了对齐) */
unsigned size; /* size of this block */
} s;
Align x; /* force alignment of blocks */
};
typedef union header Header;
在该联合中,Align 字段永远不会被使用,它仅仅用于强制每个头部在最坏的情况下满足对齐要求
在 malloc 函数中,请求的长度(以字符为单位)将被舍入,以保证它是头部大小的整数倍
实际分配的块将多包含一个单元,用于头部本身
实际分配的块的大小将被记录在头部的 size 字段中malloc 函数返回的指针将指向空闲空间,而不是块的头部
用户可对获得的存储空间进行任何操作,但是,如果在分配的存储空间之外写入数据,则可能会破坏块链表

图 8-2malloc 返回的块
其中的 size 字段是必需的,因为由 malloc 函数控制的块不一定是连续的
因此无法通过指针算术运算计算其大小
变量 base 表示空闲块链表的头部
第一次调用 malloc 函数时,freep 为 NULL
系统将创建一个退化的空闲块链表,它只包含一个大小为 0 的块,且该块指向它自己
任何情况下,当请求空闲空间时,都将搜索空闲块链表
搜索从上一次找到空闲块的地方(freep)开始,该策略可以保证链表是均匀的
如果找到的块太大,则将其尾部返回给用户,这样,初始块的头部只需要修改 size 字段即可
在任何情况下,返回给用户的指针都指向块内的空闲存储空间,即比指向头部的指针大一个单元
static Header base; /* empty list to get started */
static Header *freep = NULL; /* start of free list */
/* malloc: general-purpose storage allocator */
void *malloc(unsigned nbytes)
{
Header *p, *prevp;
Header *morecore(unsigned);
unsigned nunits;
nunits = (nbytes+sizeof(Header)-1)/sizeof(header) + 1;
if ((prevp = freep) == NULL) {
/* no free list yet */
base.s.ptr = freeptr = prevptr = &base;
base.s.size = 0;
}
for (p = prevp->s.ptr; ; prevp = p, p = p->s.ptr) {
if (p->s.size >= nunits) {
/* big enough */
if (p->s.size == nunits) /* exactly */
prevp->s.ptr = p->s.ptr;
else {
/* allocate tail end */
p->s.size -= nunits;
p += p->s.size;
p->s.size = nunits;
}
freep = prevp;
return (void *)(p+1);
}
if (p == freep) /* wrapped around free list */
if ((p = morecore(nunits)) == NULL)
return NULL; /* none left */
}
}
函数 morecore 用于向操作系统请求存储空间,其实现细节因系统的不同而不同
因为向系统请求存储空间是一个开销很大的操作,因此,我们不希望每次调用 malloc 函数时都执行该操作
基于这个考虑,morecore 函数请求至少 NALLOC 个单元,这个较大的块将根据需要分成较小的块
在设置完 size 字段之后,morecore 函数调用 free 函数把多余的存储空间插入到空闲区域中
UNIX 系统调用 sbrk(n) 返回一个指针,该指针指向 n 个字节的存储空间
如果没有空闲空间,尽管返回 NULL 可能更好一些,但 sbrk 调用返回 -1
必须将 -1 强制转换为 char * 类型,以便与返回值进行比较
而且,强制类型转换使得该函数不会受不同机器中指针表示的不同的影响
但是,这里仍然假定,由 sbrk 调用返回的指向不同块的多个指针之间可以进行有意义的比较
ANSI 标准并没有保证这一点,它只允许指向同一个数组的指针间的比较
因此,只有在一般指针间的比较操作有意义的机器上,该版本的 malloc 函数才能够移植
#define NALLOC 1024 /* minimum #units to request */
/* morecore: ask system for more memory */
static Header *morecore(unsigned nu)
{
char *cp, *sbrk(int);
Header *up;
if (nu < NALLOC)
nu = NALLOC;
cp = sbrk(nu * sizeof(Header));
if (cp == (char *) -1) /* no space at all */
return NULL;
up = (Header *) cp;
up->s.size = nu;
free((void *)(up+1));
return freep;
}
free 函数从 freep 指向的地址开始,逐个扫描空闲块链表,寻找可以插入空闲块的地方
该位置可能在两个空闲块之间,也可能在链表的末尾
在任何一种情况下,如果被释放的块与另一空闲块相邻,则将这两个块合并起来
合并两个块的操作很简单,只需要设置指针指向正确的位置,并设置正确的块大小就可以了
/* free: put block ap in free list */
void free(void *ap)
{
Header *bp, *p;
/* * 让 bp 指向待释放块的 header * 因为 ap 指向的是待释放块中将被当做空闲的部分,把它强制类型转换为 Header * 指针后减 1 * 相当于让指针位置向低地址方向移动了 1 个 Header 类型数据的大小 * 刚好就移动到了待释放块中 header 的起始位置 */
bp = (Header *)ap - 1; /* point to block header */
/* * freep 指向空闲块链表的起始位置(指向的是块的 header,而不是空闲部分) * for 循环遍历空闲块链表:for (p = freep; ...; p = p->s.ptr) * 条件 bp > p && bp < p->s.ptr 表示待释放块已经位于当前遍历到的空闲块与下一个空闲块之间了 * 条件 p >= p->s.ptr 表示已经遍历到空闲块链表的尾结点了 * 条件 (p >= p->s.ptr) && (bp > p || bp < p->s.ptr) 表示待释放块位于尾结点之后或头结点之前 */
for (p = freep; !(bp > p && bp < p->s.ptr); p = p->s.ptr)
if (p >= p->s.ptr && (bp > p || bp < p->s.ptr))
break; /* freed block at start or end of arena */
if (bp + bp->size == p->s.ptr) {
/* join to upper nbr */
// 如果待释放块和 p->s.ptr 挨着,则将 p->s.ptr 并入待释放块
bp->s.size += p->s.ptr->s.size;
bp->s.ptr = p->s.ptr->s.ptr;
} else
// 如果待释放块和 p->s.ptr 没有挨着,则将待释放块的 next 指针指向 p->s.ptr
bp->s.ptr = p->s.ptr;
if (p + p->size == bp) {
/* join to lower nbr */
// 如果待释放块和 p 挨着,则将待释放块并入 p
p->s.size += bp->s.size;
p->s.ptr = bp->s.ptr;
} else
// 如果待释放块和 p 没有挨着,则将 p 的 next 指针指向 bp
p->s.ptr = bp;
/* * freep 指向链表中任何节点都可以,因为链表本身是个环,任何节点都可以是头结点 * 这里让其指向 p,是因为如果原来的头结点被合并到某个块的后边,那么原来指向头结点的指针就没有正确指向块的起始位置了 * 而无论如何合并,p 都是某个空闲块的起始位置 * 因此 freep = p 保证了 freep 能指向某个空闲块的起始位置 */
freep = p;
}
虽然存储分配从本质上是与机器相关的,但是,以上的代码说明了如何控制与具体机器相关的部分,并将这部分程序控制到最少量typedef 和 union 的使用解决了地址的对齐(假定 sbrk 返回的是合适的指针)问题
类型的强制转换使得指针的转换是显式进行的,这样做甚至可以处理设计不够好的系统接口问题
虽然这里所讲的内容只涉及到存储分配,但是,这种通用方法也适用于其它情况
边栏推荐
猜你喜欢
![Analysis on the current situation of China's antiarrhythmic drug industry in 2021: domestic R & D is further [figure]](/img/48/714f1712f4c2d727dd49cbc6631abf.jpg)
Analysis on the current situation of China's antiarrhythmic drug industry in 2021: domestic R & D is further [figure]

The market share of packaged drinking water has been the first for eight consecutive years. How does this brand DTC continue to grow?

Project training of Software College of Shandong University rendering engine system basic renderer (VII)

Scanpy(六)空间转录组数据的分析与可视化

从斐波那契数列求和想到的俗手、本手和妙手

Analysis on the development status and direction of China's cultural tourism real estate industry in 2021: the average transaction price has increased, and cultural tourism projects continue to innova

【工具推荐】个人本地 markdown 知识图谱软件 Obsidian

MYSQL---服务器配置相关问题

leetcode-54. Spiral matrix JS

Huawei equipment is configured with CE dual attribution
随机推荐
记一篇IT培训日记067-好人感恩,坏人无错
Apache Kylin 历险记
Escape rules and examples of go
[tool recommendation] personal local markdown knowledge map software
Global and Chinese market of vascular prostheses 2022-2028: Research Report on technology, participants, trends, market size and share
超详细干货!Docker+PXC+Haproxy搭建高可用强一致性的MySQL集群
acwing 800. 数组元素的目标和
聊聊事件监听那些事-上
思考游戏王决斗链接中抽卡概率问题
< 山东大学软件学院项目实训 > 渲染引擎系统——基础渲染器(六)
联通网管协议框图
Review of the development of China's medical beauty (medical beauty) industry in 2021: the supervision is becoming stricter, the market scale is expanding steadily, and the development prospect is bro
Global and Chinese market of medical ECG telemetry equipment 2022-2028: Research Report on technology, participants, trends, market size and share
leetcode-54. Spiral matrix JS
In 2020, the demand for strain sensors in China will reach 9.006 million, and the market scale will reach 2.292 billion yuan [figure]
Axure RP 9 for Mac(交互式产品原型设计工具)中文版
位运算例题(待续)
Match single character
redis 通用命令
Use of packet capturing tool Fiddler: simulating speed limit test process in weak network environment