前言
本项目适合C语言基础概念学习结束后,想要更深层次的理解尤其是内存方面的知识的同学。该项目是我自己构建框架并编写的,所以可能会有很多不严谨的地方,也欢迎大家多多交流指正。同时这篇文章也是主要更侧重记录我当时想不通的地方。毕竟现在有AI了,它可以高效直接生成代码和思路,不需要我再搬运。
内存池搭建项目说明
主要实现C语言的中的malloc()函数,calloc()函数,realloc()函数以及free()函数。其中内存池我们声明一个char型数组作为全局变量。
思维框架

这是我最开始做的思维导图,但是到后面也有一些更改,大致情况就是这样了,下面介绍中我也会逐步讲解我的思路
项目框架
c
./memory_ctl.h
-----------------------------------------------------
#ifndef MEMORY
#define MEMORY
#include<stdio.h>
#include<stdlib.h>
//初始化内存池
void init();
//申请内存
void* my_malloc(size_t size);
//展示内存分配情况
void show(void* pool);
//回收内存
void my_free(void* ptr);
//实现申请空间的扩容缩容
void* my_realloc(void* ptr, size_t new_size);
//申请内存并进行初始化
void* my_calloc(size_t num, size_t size);
#endif
框架搭建完其实你发现主要实现malloc和free函数就可以完成基本的内存管理了,realloc和calloc中完全可以调用他们可以省去不少力气,所以在实现时还是要注意好的编写规范,尽可能辅助函数功能单一,可以有很好的复用效果。
在这里我没有给出管理内存的内存块的结构体定义,理由是,内存管理完全由操作系统自己管理,所以c语言中只给了我们申请和释放内存的调用接口,所以在我们实现时,也只在头文件中声明接口就可以了,不允许外部管理内存。
项目实现
1. 数据结构定义
这里我选择用链表来进行维护内存空间,我的想法是开始从大的内存块开始分裂内存块,不断从低地址开始分裂调出,链表中的块始终保存的是高地址部分或是留下高地址碎片,当释放内存的时候选择用头插法再插入链表,这样还是保持的是低地址部分再链表头,高地址部分在链表尾,可以很方便的做合并工作。
但是,这是还是考虑的不够周全,会在后面的实现中体现出来
c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define POOL_SIZE 1024
//空闲块
typedef struct free_block {
size_t size; // 记录内存块中的内存空间
struct free_block* next; //下一个空闲块
}free_block;
//空闲块链表
typedef struct list {
int block_num; // 链表中空闲块的数量
size_t size; // 整个内存中的可申请的内存空间
free_block* head; // 链表头指针
}List;
//内存池
char pool[POOL_SIZE];
//空闲块链表
List* list = (List*)pool;
2. 初始化内存池
初始化内存池,需要注意,内存池的大小并不是POOL_SIZE,因为我们的链表空间也是申请在这里的所以需要减去一个sizeof(List),同样此时内存池中只有一个最大的可分配内存块,内存块的大小也要减去表头还要减去自己的块头。
c
void init() {
list->size = POOL_SIZE - sizeof(List);
list->head = (free_block*)(pool + sizeof(List));
list->head->next = NULL;
list->head->size = list->size - sizeof(free_block);
list->block_num = 1;
//未初始化成功 退出程序
if (list->head == NULL) exit(1);
}
3. 实现my_malloc()函数
参考creference中malloc()函数进行定义

异常情况处理**
在函数定义中,首先要考虑到异常情况,当申请内存空间非法或超过内存池时,会导致程序异常,所以我直接退出了程序,当然也可选择放回NULL,让用户自己处理错误。
选择合适的空闲块分配
选择合适的块就是从链表中找到一个申请空间+块头的空间,因为我们需要用块来维护整个内存管理,不要单纯只按照申请空间大小寻找。
学习操作系统的时候,会学到什么最坏适应算法,最佳适应算法什么的,这里就排上用场了,我这里实现时首次适应算法
这里采用双指针,因为合适的块之后,你是要把他调出的,所以要记录他的前置节点,保证不会断链。在这里需要注意考虑特殊请情况就是如果链表中只有一个块时我开始最后一句只有p->next = q->next;就是因为没有考虑到,才加了判断语句
块分裂
最佳适应算法,很难找到刚好合适的块,所以一般都需要我们来进行块分裂的操作。该函数 我会逐句讲一下我的思路
- 进入函数就意味着链表中的块要增加,不考虑分裂失败的情况,这个交给外部判断
- 最开始我们讲从低地址为开始分裂,当前块的首地址就是最低的地址位,那么分裂后的第一块就是我们需要分配的,而需要分配的内存我们是知道的,由此我们可以计算出分裂后第二块内存块的地址。
这里需要注意有一个复杂的类型转换,首先就是我们计算内存地址范围时一定要在(char *)情况下进行的因为对地址进行加减是按照这个数据类型的size操作而不是字节,char是1字节,所以要转化位char,计算完地址后还要再转换为块结构的地址类型。- 分裂之后需要对新块的信息进行修改
- 之后用指针指向原块后面的块保证不断链。
- 分裂工作完成,原块缩小所以修改信息
- 同样将原块的指针指向新块
c
//块分裂
void splite(free_block* block, size_t size) {
list->block_num++;
//块分裂成使用块和新空闲块,已有当前块地址,直接计算新空闲块地址即可
free_block* new_block = (free_block*)((char*)block + sizeof(free_block) + size);
//新空闲块地址 = 原块地址 + 表头 + 申请空间
new_block->size = block->size - sizeof(free_block) - size;
new_block->next = block->next;
block->size = size;
block->next = new_block;
}
c
//首次适应
free_block* find_block(size_t size) {
free_block* p = NULL, * q = list->head;
while (q && q->size < size) { //寻找大于申请块的大小的块
p = q;
q = q->next;
}
if (q == NULL) { //没有合适的块
printf("错误:内存不足,分配失败!\n");
exit(1);
}
else { //找到合适的块,调整空闲链表指针
if (q->size > size + sizeof(free_block)) splite(q, size); // 内存块大于申请空间则分裂块
if (!p) list->head = q->next; //如果只有一个块会断链
else p->next = q->next;
}
return q;
}
c
void* my_malloc(size_t size) {
if (size <= 0 || list->size < size + sizeof(free_block)){
printf("错误:申请空间过大或小于1!\n");
exit(1);
}
free_block* find_res = NULL;
if (find_res = find_block(size)) { // 找到合适的块
list->block_num--;
list->size -= (sizeof(free_block) + size);
return (char*)find_res + sizeof(free_block);
}
return find_res;
}
到这里已经就实现了malloc的实现,我们可以尝试着用它来申请空间了,可以看到下面的效果图,和我们正常使用是一样的。ok!我们再接再厉,来实现free()函数。

3. 实现my_free()函数
参考creference中的free()定义

回收空间传入的时候我们我们要清楚ptr指向的是数据空间,而我们管理的内存块的块头在他前面,所以我们需要找到块头的位置。与分裂块一样,在进行地址操作时候要转换成char型的地址类型操作,找到地址后再转换成对应的类型,找到块之后,就简单了。就是一个纯粹的链表操作,头插法插入空闲链表,修改空闲块链表中数据,就完成操作了。
可以看到在代码中我加入了一个回收可视化的操作,用来确认真的被回收了,我们可以打开调试器来观测一下

c
void my_free(void* ptr) {
if (ptr == NULL) return;
//找到表头
free_block* block = (free_block *)((char *)ptr - sizeof(free_block));
//回收可视化
for (int i = 0; i < block->size; i++) {
*((char*)ptr + i) = 'f';
}
//空闲块链表数据修改
list->size += block->size;
list->block_num++;
block->next = list->head;
list->head = block;
}
这样我们就完成了基本的内存管理方法。为了更好观察空闲块链表的情况,可以写一个show_memory函数来查看
3. 实现show_memory()函数
主要观测块的数量,以及每块的内存大小(这里是不算k块头的空间),和块的地址
c
void show_memory() {
int num = list->block_num;
free_block* t = list->head;
printf("\n当前内存池共有%d块内存块", num);
int i = 1;
while (t) {
printf("\n------------------------------------------------------------------------------\n");
printf("第%d块内存块:\n大小: %zu\t 地址: &%p ~ &%p", i++, t->size, t, (char*)t + sizeof(free_block) + t->size);
printf("\n------------------------------------------------------------------------------\n");
t = t->next;
}
}
我们申请了5块内存回收了3块,所以链表中还有4块。因为我们使用的是首次适应算法,所以他们的地址是相连的。

3. my_free()函数优化
学习操作系统的时候我们学到了这部分,那就是首次适应算法使用多了之后,会导致内存块有很多碎片而且可用的内存块会越来越小,现在我们就发现了这个问题,有很多物理相邻的块,却从逻辑上分开了,使得我们有空间无法使用,所以我们要优化一下my_free函数,让其回收块之后。检查链表有无物理相邻的块,进行合并。
优化思路:
首先回收的块要先在链表中寻找是否有相邻的地址
有的话就参与合并,没有插入链表
那么我么就要实现找邻居和合并两个功能:
search_nerghbor()函数
在这里我最开始写的时候就有一个误区,寻找的时候只寻找了当前块之后的高地址位寻找,也就是向后寻找,在实际情况中也可能存在他前面的块,所以也要向前寻找。寻找过程不能理解,主要还是在地址操作过程中要注意转换成char*。
这里我还引入了一个flag参数,这是我后加的,主要是为了区分,找到的块是在前还是在后,有了这一步,后边的合并过程会简单很多。
c
//寻找物理相邻的块
free_block* search_neighbor(free_block* block, int *flag) {
free_block* t = list->head;
//向块后寻找
while (t) {
if ((char*)block + sizeof(free_block) + block->size == (char*)t) {
*flag = 1;
return t;
}
t = t->next;
}
t = list->head;
//向块前寻找
while (t) {
if ((char*)t + sizeof(free_block) + t->size == (char*)block) {
*flag = 0;
return t;
}
t = t->next;
}
return NULL;
}
merge()函数
找到相邻块就可以进行合并了,其实就是和上面splite()函数相反的过程,这里可以注意看传参,是明确区分了两个块的位置,这里就是刚才flag参数的作用了,在外部用flag做判断,然后针对性传参。这里需要多提一句的是需不需要改post_block指针,因为已经合并了,post_block已经找不到,所以可以不用管,但是我又总觉得会不会有什么奇怪的情况,会导致指针混乱。所以加上了这条语句。
c
//合并回收空闲块
free_block* merge(free_block *pre_block, free_block *post_block) {
//合并内存空间
pre_block->size += post_block->size + sizeof(free_block);
pre_block->next = post_block->next;
//修改空闲块表空间
list->size += sizeof(free_block);
list->block_num--;
//修改post块指针
post_block->next = NULL;
return pre_block;
}
优化后的my_free()
这里要注意的是我们查找邻居的操作是一个循环操作,因为链表里面可能有多个块,所以不是一次合并可以完成的,这次再查看一下运行结果
c
//实现free()
void my_free(void* ptr) {
if (ptr == NULL) return;
//找到表头
free_block* block = (free_block *)((char *)ptr - sizeof(free_block));
//回收可视化
for (int i = 0; i < block->size; i++) {
*((char*)ptr + i) = 'f';
}
//空闲块链表数据修改
list->size += block->size;
list->block_num++;
//寻找物理相邻块
int flag = 0, modify = 1;
free_block* neighbor;
while (neighbor = search_neighbor(block, &flag)) {
//有相邻块合并
if (neighbor) {
if (flag)block = merge(block, neighbor);
else merge(neighbor, block);
modify = 0;
}
}
//如果没有参与合并回收块加入空闲块链表
if(modify){
block->next = list->head;
list->head = block;
}
}
这里我们查看运行结果,回收了两块内存,链表应该有三块,但是显示只有一块,说明我们是合并成功了。这里我们还打印了其他相关信息,用来计算是否正确

内存池大小:POOLSIZE=1024实际大小(容量−表头−块头)=1024−24−16=984 内存池大小:POOLSIZE = 1024\\ 实际大小(容量-表头-块头)=1024-24-16 = 984 内存池大小:POOLSIZE=1024实际大小(容量−表头−块头)=1024−24−16=984
内存没有问题,到这里我们已经实现了内存管理的基本功能,剩下的calloc与realloc相对就会简单很多,因为它可以直接调用malloc和free。
实现calloc()
只是malloc函数加了一个初始化,所以不过多赘述
c
void* my_calloc(size_t num, size_t size) {
// 防止整数溢出
if (num == 0 || size == 0) return NULL;
if (SIZE_MAX / num < size) {
printf("错误: calloc 大小溢出\n");
return NULL;
}
size_t total_size = num * size;
char* ch = (char*)my_malloc(total_size);
if (ch != NULL) {
// 使用 memset 更高效
memset(ch, 0, total_size);
}
return (void*)ch;
}
实现my_realloc()
realloc的实现需要考虑多种情况,思路捋清楚就可以了,一个是缩容情况,一个是扩容情况
- 缩容
- 如果缩小的话剩下的空间不够块头怎么办,涉及到碎片处理
- 剩下的空间足够,这里我最开始调用了split()函数但是出现了问题,具体原因忘记了,应该是指针指向的问题
2.扩容- 有物理相邻的块
- 没有物理相邻的块
c
void* my_realloc(void* ptr, size_t new_size) {
if (ptr == NULL) return my_malloc(new_size);
if (new_size == 0) {
my_free(ptr);
return NULL;
}
free_block* block = (free_block*)((char*)ptr - sizeof(free_block));
// 1. 缩容情况
if (new_size < block->size) {
// 只有当剩余空间足够容纳一个新的空闲块表头时才分裂
if (block->size - new_size > sizeof(free_block)) {
// 计算新空闲块的位置
free_block* new_free_block = (free_block*)((char*)block + sizeof(free_block) + new_size);
new_free_block->size = block->size - new_size - sizeof(free_block);
new_free_block->next = NULL;
block->size = new_size;
my_free((char*)new_free_block + sizeof(free_block));
}
return ptr;
}
// 2. 扩容情况
else if (new_size > block->size) {
int flag = 0;
free_block* neighbor = search_neighbor(block, &flag);
// 后面有相邻空闲块,且合并后空间足够
if (neighbor && flag == 1 && (block->size + sizeof(free_block) + neighbor->size >= new_size)) {
// 合并邻居到当前块
block = merge(block, neighbor);
free_block* p = list->head;
if (p == neighbor) {
list->head = neighbor->next;
}
else {
while (p && p->next != neighbor) p = p->next;
if (p) p->next = neighbor->next;
}
// 2. 扩展 block
block->size += sizeof(free_block) + neighbor->size;
// 3. 更新数据
list->block_num--;
list->size -= (sizeof(free_block) + neighbor->size); // 实际上是吞并了 neighbor
return ptr;
}
// 无法扩展,需要搬运
else {
void* temp = my_malloc(new_size);
if (temp == NULL) return NULL; // 分配失败
// 拷贝数据
memcpy(temp, ptr, block->size);
// 释放内存
my_free(ptr);
return temp;
}
}
// new_size == block->size
return ptr;
}
至此已经实现了所有的内存管理功能的所有功能,这是我最后的运行测试

最后的问题
我的realloc实现还是有问题,另外就是在free()回收的时候也会,也存在一定的顺序因素,这就是最开始说的问题了,解决合并问题,最好还是用双链表去解决,但是我定义的是单链表,这就很难处理。最后我是提交给了AI,总体还是肯定了我的核心逻辑和代码框架,但不免有很多小bug。
AI最终版
c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#define POOL_SIZE 4096 // 增大一点以便测试
#define ALIGNMENT 8 // 内存对齐字节数 (64位系统通常为8)
// 宏用于对齐大小
#define ALIGN(size) (((size) + (ALIGNMENT - 1)) & ~(ALIGNMENT - 1))
// 空闲块结构
typedef struct free_block {
size_t size; // 用户可用空间的大小
struct free_block* next; // 下一个空闲块
} free_block;
// 空闲块链表头(位于池的起始位置)
typedef struct {
int block_num; // 空闲块数量
size_t total_free_size; // 总空闲大小
free_block* head; // 链表头指针
} List;
// 内存池
char pool[POOL_SIZE];
// 全局链表指针
List* list;
// 初始化
void init() {
list = (List*)pool;
list->block_num = 1;
list->total_free_size = POOL_SIZE - sizeof(List);
// 第一个空闲块紧跟在 List 后面
list->head = (free_block*)(pool + sizeof(List));
list->head->size = list->total_free_size - sizeof(free_block);
list->head->next = NULL;
}
// 块分裂
// 注意:这里的 block 是空闲链表中的一个节点
void split(free_block* block, size_t size) {
// 剩余空间必须足够容纳一个新的空闲块头 + 最小有效载荷
size_t remaining = block->size - size;
if (remaining < sizeof(free_block) + ALIGNMENT) {
return; // 空间太小,不分裂,直接分配整个块
}
// 创建新空闲块
free_block* new_block = (free_block*)((char*)block + sizeof(free_block) + size);
new_block->size = remaining - sizeof(free_block);
new_block->next = block->next;
// 更新原块
block->size = size;
block->next = new_block;
// 更新全局统计
list->block_num++;
list->total_free_size -= sizeof(free_block);
}
// 首次适应算法查找
free_block* find_block(size_t size) {
free_block* p = NULL; // 前驱节点
free_block* q = list->head;
while (q && q->size < size) {
p = q;
q = q->next;
}
if (q == NULL) {
return NULL; // 找不到合适的块
}
// 找到块 q,准备从链表中摘除或修改
// 1. 尝试分裂
split(q, size);
// 2. 从空闲链表中移除 q (因为 q 将被分配给用户)
if (p == NULL) {
list->head = q->next;
} else {
p->next = q->next;
}
// 3. 更新全局统计 (减去分配出去的大小)
list->block_num--;
list->total_free_size -= q->size;
return q;
}
// 寻找物理相邻的块 (仅在空闲链表中查找)
// flag: 1 表示 neighbor 在 block 后面, 0 表示 neighbor 在 block 前面
free_block* search_neighbor(free_block* block, int* flag) {
free_block* t = list->head;
char* block_addr = (char*)block;
while (t) {
char* t_addr = (char*)t;
// 检查 t 是否紧跟在 block 后面
if (block_addr + sizeof(free_block) + block->size == t_addr) {
*flag = 1;
return t;
}
// 检查 t 是否紧跟在 block 前面
if (t_addr + sizeof(free_block) + t->size == block_addr) {
*flag = 0;
return t;
}
t = t->next;
}
return NULL;
}
// 合并两个空闲块
// 前提:pre 和 post 都必须在空闲链表中,且物理相邻
void merge_blocks(free_block* pre, free_block* post) {
pre->size += sizeof(free_block) + post->size;
pre->next = post->next;
// 更新统计
list->block_num--;
list->total_free_size += sizeof(free_block);
}
// 实现 malloc
void* my_malloc(size_t size) {
if (size <= 0) return NULL;
// 对齐大小
size_t aligned_size = ALIGN(size);
if (list == NULL) init(); // 懒初始化
free_block* block = find_block(aligned_size);
if (block == NULL) {
printf("错误: 内存不足,分配失败! (请求: %zu)\n", size);
return NULL;
}
// 返回用户指针 (跳过块头)
return (void*)((char*)block + sizeof(free_block));
}
// 实现 free
void my_free(void* ptr) {
if (ptr == NULL) return;
free_block* block = (free_block*)((char*)ptr - sizeof(free_block));
// 调试:填充数据
memset(ptr, 0xDD, block->size);
// 1. 尝试与后面的空闲块合并
int flag = 0;
free_block* neighbor = search_neighbor(block, &flag);
if (neighbor && flag == 1) {
// 后面有邻居,合并 block 和 neighbor
// 注意:此时 block 还没进链表,neighbor 在链表中
// 我们需要把 block 的空间加到 neighbor 上,并更新 neighbor 的地址?
// 不,通常做法是:扩展 block,把 neighbor 从链表摘除,然后把 block 加入链表
// 从链表中摘除 neighbor
free_block* p = list->head;
if (p == neighbor) list->head = neighbor->next;
else {
while (p && p->next != neighbor) p = p->next;
if(p) p->next = neighbor->next;
}
// 扩展 block (逻辑上合并)
block->size += sizeof(free_block) + neighbor->size;
// 此时 neighbor 废弃,block 变大了
}
// 2. 尝试与前面的空闲块合并
// 重新搜索,因为上面的合并可能改变了布局(虽然这里 block 地址没变)
// 但如果上面合并了,block 现在包含了原来的 neighbor,我们需要再次检查新的后面是否有邻居
// 为了简化,我们只处理一次合并,或者循环处理。这里处理"前邻居"情况。
// 如果刚才合并了后面,现在的 block 更大了,可能还能合并更后面的?
// 简单的做法:先处理前邻居。
neighbor = search_neighbor(block, &flag);
if (neighbor && flag == 0) {
// 前面有邻居,将 block 合并进 neighbor
// neighbor 在链表中,block 不在
// 扩展 neighbor
neighbor->size += sizeof(free_block) + block->size;
// block 废弃,不需要把 block 加入链表,因为 neighbor 已经在链表中了
// 只需要更新统计信息
list->total_free_size += sizeof(free_block); // 实际上是把 block 的头变成了数据区
return; // 结束,neighbor 已经在链表里了
}
// 3. 如果没有合并,或者只合并了后面(此时 block 是独立的),将 block 加入链表头部
block->next = list->head;
list->head = block;
list->block_num++;
list->total_free_size += block->size;
}
// 实现 calloc
void* my_calloc(size_t num, size_t size) {
if (num == 0 || size == 0) return NULL;
if (SIZE_MAX / num < size) return NULL; // 溢出检查
size_t total = num * size;
void* ptr = my_malloc(total);
if (ptr) {
memset(ptr, 0, total);
}
return ptr;
}
// 实现 realloc
void* my_realloc(void* ptr, size_t new_size) {
if (ptr == NULL) return my_malloc(new_size);
if (new_size == 0) {
my_free(ptr);
return NULL;
}
free_block* block = (free_block*)((char*)ptr - sizeof(free_block));
size_t old_size = block->size;
size_t aligned_new_size = ALIGN(new_size);
// 1. 缩容
if (aligned_new_size < old_size) {
// 只有当剩余空间足够大时才分裂
if (old_size - aligned_new_size > sizeof(free_block) + ALIGNMENT) {
// 模拟 split 逻辑,但不从链表摘除(因为 block 不在链表中)
// 实际上我们需要把多余的部分变成一个空闲块并 free 掉
free_block* new_free = (free_block*)((char*)block + sizeof(free_block) + aligned_new_size);
new_free->size = old_size - aligned_new_size - sizeof(free_block);
block->size = aligned_new_size;
// 释放多余部分
my_free((char*)new_free + sizeof(free_block));
}
return ptr;
}
// 2. 扩容
else if (aligned_new_size > old_size) {
int flag = 0;
free_block* neighbor = search_neighbor(block, &flag);
// 如果后面有相邻空闲块,且合并后足够大
if (neighbor && flag == 1 && (old_size + sizeof(free_block) + neighbor->size >= aligned_new_size)) {
// 从链表摘除 neighbor
free_block* p = list->head;
if (p == neighbor) list->head = neighbor->next;
else {
while (p && p->next != neighbor) p = p->next;
if(p) p->next = neighbor->next;
}
// 吞并 neighbor
block->size += sizeof(free_block) + neighbor->size;
list->block_num--;
list->total_free_size -= (sizeof(free_block) + neighbor->size); // 实际上是被占用了
return ptr;
} else {
// 无法原地扩容,需要搬运
void* temp = my_malloc(new_size);
if (temp == NULL) return NULL;
memcpy(temp, ptr, old_size);
my_free(ptr);
return temp;
}
}
// 大小相等
return ptr;
}
// 调试显示内存状态
void show_memory() {
if (!list) { printf("未初始化\n"); return; }
printf("\n========== 内存池状态 ==========\n");
printf("总空闲大小: %zu\n", list->total_free_size);
printf("空闲块数量: %d\n", list->block_num);
free_block* t = list->head;
int i = 1;
while (t) {
printf("[%d] 地址: %p, 大小: %zu\n", i++, (void*)t, t->size);
t = t->next;
}
printf("================================\n");
}
// 主函数测试
int main() {
init();
show_memory();
printf("分配 100 字节...\n");
void* p1 = my_malloc(100);
show_memory();
printf("分配 200 字节...\n");
void* p2 = my_malloc(200);
show_memory();
printf("释放 p1...\n");
my_free(p1);
show_memory();
printf("分配 50 字节 (应该复用 p1 的空间)...\n");
void* p3 = my_malloc(50);
show_memory();
printf("释放 p2, p3...\n");
my_free(p2);
my_free(p3);
show_memory(); // 此时应该合并成一个大的空闲块
return 0;
}
写在最后
关于AI写的我也知道了自己的问题,就像最开始所说,这篇文章主要侧重于我本身的错误,而不是一开始拿着答案来讲。虽然现在都可以一键生成,但我还是认为动手才能让理解更加深入。