Skip to content

docs(c): 补全动态内存、预处理器与文件 I/O 练习答案 - #165

Merged
Charliechen114514 merged 6 commits into
Awesome-Embedded-Learning-Studio:mainfrom
xiaoshuaijie:exe_14
Aug 27, 2026
Merged

docs(c): 补全动态内存、预处理器与文件 I/O 练习答案#165
Charliechen114514 merged 6 commits into
Awesome-Embedded-Learning-Studio:mainfrom
xiaoshuaijie:exe_14

Conversation

@xiaoshuaijie

Copy link
Copy Markdown
Contributor

补充 C 教程第 14 至 16 章的练习参考答案、可运行示例和验证步骤,覆盖动态内存、预处理器与多文件工程、文件 I/O 三个连续主题

  • 将 IntVec 初始化接口调整为返回状态码,显式处理 malloc 失败。
  • 增加动态数组完整演示,展示容量从 4 扩容至 8、16,以及释放后指针清空的结果。
  • 为固定大小内存池补充参考实现:
    • 使用静态内存池实例,限制块大小、块数量和实例数量;
    • 使用 max_align_t 保证内存块满足基础类型的对齐要求;
    • 覆盖创建、分配、释放、销毁、内存块复用和满池处理。
  • 为 malloc/free 跟踪包装器补充参考实现:
    • 记录分配地址、大小、文件名和行号;
    • 检测重复释放及未登记地址;
    • 通过 atexit 在程序正常退出时输出内存泄漏报告;
    • 提供 TMALLOC / TFREE 宏简化调用。
  • 将本章预计阅读时间从 6 分钟调整为 12 分钟。
  • 将示例编译选项改为 -std=gnu11,以支持参考答案中的 GNU 扩展宏。
  • 补充多文件模块化练习参考答案:
    • 提供 math_utils.h、math_utils.c 与 main.c;
    • 实现 clamp_int 和 count_digits;
    • 给出直接编译与静态库打包、链接两种构建方式。
  • 补充 DEBUG_LOG 宏练习参考答案:
    • Debug 模式输出源文件、行号和格式化消息;
    • Release 模式通过 NDEBUG 展开为空操作;
    • 提供 Debug / Release 编译命令及预期输出。
  • 将本章预计阅读时间从 9 分钟调整为 30 分钟。
  • 为配置文件解析器补充完整实现:
    • 跳过空行与 # 注释;
    • 清理 key/value 两端空白;
    • 处理超长行、非法参数、文件读取错误和字段截断;
    • 提供示例配置、编译命令与运行输出。
  • 为文件复制工具补充完整实现:
    • 使用二进制模式和 4 KiB 缓冲区复制;
    • 拒绝源文件与目标文件相同的情况;
    • 处理空文件、读写失败和关闭失败;
    • 复制失败时清理不完整目标文件;
    • 提供进度显示、内容校验及异常场景验证命令。
  • PYTHONIOENCODING=utf-8 .venv\Scripts\python.exe scripts\validate_frontmatter.py
    • 981 个 Markdown 文件全部通过前置元数据校验。
  • markdownlint
    • 当前环境未安装 markdownlint 命令,无法执行。
  • git diff --check
    • 当前提交包含 11 处尾随空白,建议合并前清理。
  • 第 15 章参考答案使用 GNU statement expression 和 typeof 实现 MAX / MIN,因此需要 gnu11,不再是严格 C17;请确认这符合教程的跨平台定位。
  • 文件复制示例依赖 Linux/POSIX 的 sys/stat.h、st_dev 和 st_ino,与章节标注的 Linux x86_64 环境一致。

@xiaoshuaijie

Copy link
Copy Markdown
Contributor Author

对于这次的答案,我对于第16章文件 I/O 与标准库概览的理解和运用没有到位,大部分是借助ai完成的,自己写不出来,提交的参考答案我认真阅读过感觉是没有问题,同时我对第15章的环境由-std=C17改为了-std=gnu11,为了拓展typedef这种安全的保护,我在嵌入式开发中常用,同时在第14章的练习较为难理解的部分添加了自己的理解文字说明,辛苦老师了

@xiaoshuaijie

Copy link
Copy Markdown
Contributor Author

现在正式进行C++的学习了,进阶的部分后面有时间再看哈哈

@Charliechen114514

Copy link
Copy Markdown
Member

没事没事,因为这个练习 @owollz4 就吐槽过梯度有点大,这个我的确是按照很熟悉的人复习写的,没有顾虑到。今天刚下班。我稍后审查一下您提交的两个PR

@Charliechen114514

Copy link
Copy Markdown
Member

现在正式进行C++的学习了,进阶的部分后面有时间再看哈哈

okok,其实如果是嵌入式开发,进阶先于C++,大部分公司似乎还在用C,哈哈。我也是因为这个顾虑跑去做C-Journey

@Charliechen114514

Copy link
Copy Markdown
Member

这个PR我看了一下哈,嗯,好像看起来不太对

必修

1. DEBUG_LOG 的展示输出行号与代码不符15-preprocessor-and-multifile.md:426

按文中代码实际编译运行,输出是:

[main.c:7] 开始运行,初始值 count = 0
[main.c:10] 第 0 次循环
[main.c:11] 第 1 次循环
[main.c:11] 第 2 次循环
[main.c:15] 结束,最终 count = 3
[main.c:16] 这是不带额外参数的中文消息

上面是我按文中展示块抄的;实际是 [main.c:7][main.c:10](循环三行)、[main.c:14][main.c:15]。文中展示的是 8/11/15/16,整体比真实值大 1,应该是生成输出时的 main.c 和最终版差了一行。以实际输出为准改一下展示块。

这边的建议是不写死,提示一下这个跟代码所在行数有关系比较好。

2. bash 代码块里混进了裸文本16-file-io-and-stdlib.md:553

heredoc 写配置文件、「编译运行:」、gcc 命令、运行命令全在同一个 ```bash 块里。「编译运行:」这行会被 shell 当成命令执行,读者整块复制粘贴直接 command not found。拆成两个块:heredoc 一个,「编译运行:」放外面,编译加运行一个块(和其他答案的排版一致)。

3. 正文 feof 段落被改出语病16-file-io-and-stdlib.md:122

原句「feof 只有在读取操作已经失败之后才会返回真。正确的做法是直接检查读取函数的返回值:」本来是通顺准确的,现在改成「…当feof 读到EOF时,会返回零后再次执行feof时才会返回1.将正确的做法是…」,既不通顺(多了一个「将」),表述也绕。建议恢复原句;如果想补充细节,另起一句写清楚再单独打磨。

4. 15 号文的编译标准自相矛盾15-preprocessor-and-multifile.md:46

正文在 main 上只有 -std=c17,这 6 条 -std=gnu11 命令全是本次答案引入的,环境声明也被从 c17 改成了 gnu11;但中间那个 commit 的标题是「统一教程编译选项为标准 C17 并开启 -Wextra -Wpedantic」,名不副实。而且我实测了两点:

  • MAX/MIN({ }) 语句表达式宏在 -std=c17 -Wall -Wextra 下零警告编译(「必须 gnu11」不成立);
  • 但只要加 -Wpedantic-std=gnu11 下照样警告「ISO C forbids braced-groups within expressions」;DEBUG_LOG 零参数调用也会警告「ISO C99 requires at least one argument for the '...'」。

也就是说没有一套旗标能既保留这些扩展又保持全仓库统一的零警告。比较推介练习 1 答案去掉题面没要求的 MAX/MIN GNU 宏(clamp_int 用两个 if 或比较表达式就够),DEBUG_LOG 演示里的零参数调用改成带至少一个参数;这样 15 号文可以回到 -std=c17 -Wall -Wextra -Wpedantic 零警告,环境声明恢复 c17,和 11-14、16 号文统一。

5. 练习 3(内存池)的答案没有按题面要求的数据结构做14-dynamic-memory.md:388

题面写的是「用链表管理空闲块——每个空闲块的前几个字节存指向下一个空闲块的指针」,难度标注也点名「需要自学空闲链表(free list)惯用法」——这是这道题要教的东西。答案用的是静态数组加 in_use 标记位图,free list 完全没出现;还引入了 MEMORY_POOL_MAX_INSTANCES 上限的静态实例,和题面「从一块大内存里切出固定大小的块」的题意也不同。建议按 free list 重写;如果确实想保留位图版,至少要在答案里明说「这是另一种做法,题面要求的 in-place 链表是这样的:…」补上对比。

6. 练习 1(配置解析)答案的函数签名和题面不一致16-file-io-and-stdlib.md:295

题面是 size_t parse_config(const char* path, ConfigEntry* entries, size_t max_entries);,答案是 bool parse_config(..., size_t* out_count)。bool + 输出参数是合理设计,但读者照题面写完、打开答案发现函数长得不一样,会困惑。

建议修

7. intvec 答案不自包含14-dynamic-memory.md:291

「把上面的 intvec_init / intvec_push / intvec_free 粘贴到此处」——但 IntVec 的 typedef 还在题面的代码块里,我拼装时漏了它都编译不过。上一轮的答案都是完整单文件,这里建议直接给完整文件,省得读者自己拼。

8. count_digits 里藏着 printf15-preprocessor-and-multifile.md:248

函数既计算位数又自己打印,导致输出里夹着孤零零的 25 两行。计算归计算、打印归 main,输出块同步改。顺带:它的 docstring 是英文("Print and return the number of decimal digits"),全文其他注释都是中文。

9. 练习 4(tracked)的文件标注错位14-dynamic-memory.md:634

**debug_log.h** 标在了 ::: details 外面,details 里面两个代码块都没标注哪个是哪个。上一轮修过同款问题:答案开头一句引导(「完整程序分两个文件」),每个代码块前面加粗文件名。

10. ch00/03 的补充超出了读者阶段ch00/03-first-program.md:253-255

这是 hello world 那一篇,读者还没认识指针,就补了 new int{} 动态分配的初始化规则,时机太早;插入语的标点似乎有一些乱(半角括号、尾逗号);blockquote 和同文件的 > ⚠️ **踩坑预警** 格式不齐(缺空格、缺加粗、半角句号)。建议要么删掉,要么并进 250 行那句里只保留「静态和全局变量会自动零初始化」这一层,动态分配的话题留给后面的章节。

11. 14 号文两段讲解文字需要重写14-dynamic-memory.md:602834

pool_alloc 的作用类似与malloc」(「类似与」应为「类似于」,出现两次);834 行「在首次tracked_malloc是进行atexit注册mem_repor」——「是」应为「时」、「mem_repor」少了 t、括号没闭合、句末一串半角句号。

12. tracked_malloc 里往 stdout 打印分配大小14-dynamic-memory.md:725

printf("size = %zu\n", size) 让一个内存跟踪器每次分配都往标准输出喷一行,演示能看,但当工具用就是噪声。删掉,或改成 DEBUG_LOG 归入 stderr。

顺手的小问题

  • 16:342 附近:/** … **/ 收尾多了个星号; // \trim` 从左往右跳过… 这行孤立注释和上面的文档注释重复且没标点;#include` 和结构体定义行尾有一串空格
  • 16:602kBufferSize 用的是 Google 风格前缀,全文其他宏都是大写蛇形(MAX_LINETRACKED_ALLOCATION_MAX);copy 工具的代码块没标文件名,命令里叫 main.c,和练习 1 的 config_parser.c 放一起容易混
  • 16:594 一带:B 段展示输出少了 wc -c /tmp/e.bin 的输出行(0 /tmp/e.bin),但开头声明「前两段是真实终端输出」,要么补上要么把声明改准确
  • 16:108-112:正文 get_file_size 新加的注释 // 后没空格(第三个 commit 的主题就是规范注释空格,这里漏了);「将文件位置从开头移至文件初始位置」说法不准,是恢复到调用前的原位置
  • 14:66115:293:代码块内有行尾空格的行

看看我这里搞错啥了没有?

@xiaoshuaijie

Copy link
Copy Markdown
Contributor Author

我丢,这么多问题,辛苦老师了,我马上修

@Charliechen114514

Copy link
Copy Markdown
Member

我丢,这么多问题,辛苦老师了,我马上修

啊不一定准的,因为一些AI初步扫描的,昨天感觉精力不太好,就先初步审核一下就发上来了,您看看一些是不是误伤,
🤒

@xiaoshuaijie

xiaoshuaijie commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

练习3(内存池)的答案没有按题面要求的数据结构做(14-dynamic-memory.md:388)这个我当时换了,但是好像忘记保存了,不小心用了https://github.com/Awesome-Embedded-Learning-Studio/Tutorial_AwesomeModernCPP/pull/162/changes#diff-387707f91c09fa01a28d8e312425eaeff4a571e6591f44225a53e0be9bc071ec这个旧版了以为这个换回来了

@Charliechen114514

Copy link
Copy Markdown
Member

练习3(内存池)的答案没有按题面要求的数据结构做(14-dynamic-memory.md:388)这个我当时换了,但是好像忘记保存了,不小心用了https://github.com/Awesome-Embedded-Learning-Studio/Tutorial_AwesomeModernCPP/pull/162/changes#diff-387707f91c09fa01a28d8e312425eaeff4a571e6591f44225a53e0be9bc071ec这个旧版了以为这个换回来了

OKOK,这个的话,到时候您搞定了让我看看就好,如果感觉这段时间不太方便千万别勉强,可以转draft,别给自己太大压力了,好好休息第一

@xiaoshuaijie

Copy link
Copy Markdown
Contributor Author

老师,有一个问题,练习3(内存池)固定大小内存池分配器(挑战·可选),实现一个固定大小内存池:从一块大内存里切出固定大小的块,用链表管理空闲块——每个空闲块的前几个字节存指向下一个空闲块的指针这个固定大小内存池是要静态的还是malloc的动态的这两个好像都满足

这个是使用malloc分配内存池

#include <stddef.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

/* 内存块空闲时,块起始位置用于保存指向下一个空闲块的指针。 */
typedef struct PoolBlock {
    struct PoolBlock* next;
} PoolBlock;

typedef struct MemoryPool MemoryPool;

struct MemoryPool {
    unsigned char* storage; // malloc 获得的池底层存储区
    PoolBlock* free_list; // 空闲块链表的头指针
};

/*
 * 创建内存池。
 * block_size 为每个内存块的字节数,block_count 为内存块数量。
 * 创建成功返回内存池指针;参数为 0、大小计算溢出或 malloc 失败时返回 NULL。
 */
MemoryPool* pool_create(size_t block_size, size_t block_count)
{
    MemoryPool* pool;
    size_t alignment;
    size_t block_stride;
    size_t storage_size;
    size_t i;

    if (block_size == 0U || block_count == 0U) {
        return NULL;
    }

    if (block_size < sizeof(PoolBlock)) {
        block_size = sizeof(PoolBlock);
    }
    alignment = _Alignof(max_align_t);
    if (block_size > SIZE_MAX - (alignment - 1U)) {
        return NULL;
    }
    block_stride = ((block_size + alignment - 1U) / alignment) * alignment;
    if (block_count > SIZE_MAX / block_stride) {
        return NULL;
    }
    storage_size = block_stride * block_count;

    pool = malloc(sizeof(*pool));
    if (pool == NULL) {
        return NULL;
    }

    pool->storage = malloc(storage_size);
    if (pool->storage == NULL) {
        free(pool);
        return NULL;
    }

    pool->free_list = NULL;
    for (i = 0U; i < block_count; ++i) {
        PoolBlock* block = (PoolBlock*)(void*)(pool->storage +
                                               i * block_stride);

        block->next = pool->free_list;
        pool->free_list = block;
    }
    return pool;
}

/*
 * 从内存池中分配一个空闲内存块。
 * 分配成功返回内存块首地址;pool 为 NULL 或内存池已满时返回 NULL。
 */
void* pool_alloc(MemoryPool* pool)
{
    PoolBlock* block;

    if (pool == NULL) {
        return NULL;
    }

    block = pool->free_list;
    if (block == NULL) {
        return NULL;
    }

    pool->free_list = block->next;
    return (void*)block;
}

/*
 * 释放由 pool_alloc 从指定内存池分配的内存块。
 * block 必须是该池当前已分配、且尚未释放的块;传入异属地址、块内地址或重复
 * 释放违反接口前置条件,行为未定义。pool 或 block 为 NULL 时函数直接返回。
 */
void pool_free(MemoryPool* pool, void* block)
{
    if (pool == NULL || block == NULL) {
        return;
    }

    ((PoolBlock*)block)->next = pool->free_list;
    pool->free_list = block;
}

/*
 * 销毁内存池。pool 为 NULL 时函数直接返回;调用后该指针失效。
 */
void pool_destroy(MemoryPool* pool)
{
    if (pool == NULL) {
        return;
    }

    free(pool->storage);
    free(pool);
}

int main(void)
{
    enum { BLOCK_COUNT = 4 };
    MemoryPool* pool;
    int* values[BLOCK_COUNT];
    int* reused_value;
    size_t i;

    pool = pool_create(sizeof(int), BLOCK_COUNT);
    if (pool == NULL) {
        fputs("内存池创建失败\n", stderr);
        return 1;
    }

    for (i = 0U; i < BLOCK_COUNT; ++i) {
        values[i] = (int*)pool_alloc(pool);
        if (values[i] == NULL) {
            fputs("内存池分配失败\n", stderr);
            pool_destroy(pool);
            return 1;
        }
        *values[i] = (int)(i + 1U) * 10;
    }

    printf("当前数值:%d %d %d %d\n", *values[0], *values[1], *values[2],
           *values[3]);

    if (pool_alloc(pool) == NULL) {
        puts("内存池已满,下一次分配返回 NULL");
    }

    pool_free(pool, values[1]);
    reused_value = (int*)pool_alloc(pool);
    if (reused_value == NULL || reused_value != values[1]) {
        fputs("内存池复用失败\n", stderr);
        pool_destroy(pool);
        return 1;
    }
    *reused_value = 99;
    printf("复用后的数值:%d\n", *reused_value);

    pool_destroy(pool);
    return 0;
}

这个是静态的

#include <stddef.h>
#include <stdio.h>

/* 单个内存块可容纳的最大字节数。 */
#define MEMORY_POOL_MAX_BLOCK_SIZE 64U
/* 单个内存池可管理的最大内存块数量。 */
#define MEMORY_POOL_MAX_BLOCK_COUNT 16U
/* 程序可同时创建的内存池实例最大数量。 */
#define MEMORY_POOL_MAX_INSTANCES 2U
/* 内存块按照 max_align_t 的对齐要求切分。 */
#define MEMORY_POOL_ALIGNMENT _Alignof(max_align_t)
/* 为每个内存池预留最坏情况下所需的存储空间。 */
#define MEMORY_POOL_MAX_BLOCK_STRIDE                                      \
    (((MEMORY_POOL_MAX_BLOCK_SIZE + MEMORY_POOL_ALIGNMENT - 1U) /         \
      MEMORY_POOL_ALIGNMENT) * MEMORY_POOL_ALIGNMENT)

/* 内存块空闲时,块起始位置用于保存指向下一个空闲块的指针。 */
typedef struct PoolBlock {
    struct PoolBlock* next;
} PoolBlock;

/* 使用联合体确保整块池内存按 max_align_t 对齐。 */
typedef union {
    max_align_t alignment;
    unsigned char bytes[MEMORY_POOL_MAX_BLOCK_STRIDE *
                        MEMORY_POOL_MAX_BLOCK_COUNT];
} PoolStorage;


typedef struct MemoryPool MemoryPool;

struct MemoryPool {
    PoolStorage storage; // 内存池
    PoolBlock* free_list; // 空闲块链表的头指针
    size_t block_stride; // 单个内存块的实际步长(字节)
    size_t block_count; // 当前内存池可分配的最大内存块数量
    int active; // 标记该内存池是否处于启用状态
};

/* 使用静态实例代替运行时堆内存分配。 */
static MemoryPool memory_pools[MEMORY_POOL_MAX_INSTANCES];

/* 判断内存池指针是否指向本模块管理的静态内存池实例。 */
static int pool_is_managed(const MemoryPool* pool)
{
    size_t i;

    for (i = 0U; i < MEMORY_POOL_MAX_INSTANCES; ++i) {
        if (pool == &memory_pools[i]) {
            return 1;
        }
    }
    return 0;
}

/* 查找内存块在指定内存池中的下标;找到时将下标写入 index。 */
static int pool_block_index(const MemoryPool* pool, const void* block,
                            size_t* index)
{
    size_t i;

    if (pool == NULL || block == NULL || index == NULL) {
        return 0;
    }

    for (i = 0U; i < pool->block_count; ++i) {
        const void* current_block =
            (const void*)(pool->storage.bytes + i * pool->block_stride);

        if (block == current_block) {
            *index = i;
            return 1;
        }
    }

    return 0;
}

/* 判断指定内存块是否已经位于空闲链表中。 */
static int pool_block_is_free(const MemoryPool* pool, const PoolBlock* block)
{
    const PoolBlock* current;

    if (pool == NULL || block == NULL) {
        return 0;
    }

    for (current = pool->free_list; current != NULL; current = current->next) {
        if (current == block) {
            return 1;
        }
    }

    return 0;
}

/*
 * 创建内存池。
 * block_size 为每个内存块的字节数,block_count 为内存块数量。
 * 创建成功返回内存池指针;参数超出上限或无空闲实例时返回 NULL。
 */
MemoryPool* pool_create(size_t block_size, size_t block_count)
{
    MemoryPool* pool;
    size_t block_stride;
    size_t i;

    if (block_size == 0U || block_size > MEMORY_POOL_MAX_BLOCK_SIZE ||
        block_count == 0U || block_count > MEMORY_POOL_MAX_BLOCK_COUNT) {
        return NULL;
    }

    block_stride = block_size;
    if (block_stride < sizeof(PoolBlock)) {
        block_stride = sizeof(PoolBlock);
    }
    block_stride = ((block_stride + MEMORY_POOL_ALIGNMENT - 1U) /
                    MEMORY_POOL_ALIGNMENT) * MEMORY_POOL_ALIGNMENT;

    for (i = 0U; i < MEMORY_POOL_MAX_INSTANCES; ++i) {
        pool = &memory_pools[i];
        if (!pool->active) {
            size_t block_index;

            pool->block_stride = block_stride;
            pool->block_count = block_count;
            for (block_index = 0U; block_index < block_count; ++block_index) {
                PoolBlock* current_block = (PoolBlock*)(void*)(
                    pool->storage.bytes + block_index * block_stride);

                if (block_index + 1U < block_count) {
                    current_block->next = (PoolBlock*)(void*)(
                        pool->storage.bytes +
                        (block_index + 1U) * block_stride);
                } else {
                    current_block->next = NULL;
                }
            }
            pool->free_list = (PoolBlock*)(void*)pool->storage.bytes;
            pool->active = 1;
            return pool;
        }
    }

    return NULL;
}

/*
 * 从内存池中分配一个空闲内存块。
 * 分配成功返回内存块首地址;内存池无效或已满时返回 NULL。
 */
void* pool_alloc(MemoryPool* pool)
{
    PoolBlock* block;

    if (!pool_is_managed(pool) || !pool->active) {
        return NULL;
    }

    block = pool->free_list;
    if (block == NULL) {
        return NULL;
    }

    pool->free_list = block->next;
    return (void*)block;
}

/*
 * 释放由 pool_alloc 从指定内存池分配的内存块。
 * pool 或 block 无效、block 不属于该内存池或已释放时,函数直接返回。
 */
void pool_free(MemoryPool* pool, void* block)
{
    size_t index;
    PoolBlock* released_block;

    if (!pool_is_managed(pool) || !pool->active ||
        !pool_block_index(pool, block, &index)) {
        return;
    }

    released_block = (PoolBlock*)(void*)(
        pool->storage.bytes + index * pool->block_stride);
    if (pool_block_is_free(pool, released_block)) {
        return;
    }

    released_block->next = pool->free_list;
    pool->free_list = released_block;
}

/*
 * 销毁内存池并将其静态实例标记为空闲。
 * 内存池无效或已销毁时,函数直接返回。
 */
void pool_destroy(MemoryPool* pool)
{
    if (!pool_is_managed(pool) || !pool->active) {
        return;
    }

    pool->free_list = NULL;
    pool->block_stride = 0U;
    pool->block_count = 0U;
    pool->active = 0;
}

int main(void)
{
    enum { BLOCK_COUNT = 4 };
    MemoryPool* pool;
    int* values[BLOCK_COUNT];
    int* reused_value;
    size_t i;

    pool = pool_create(sizeof(int), BLOCK_COUNT);
    if (pool == NULL) {
        fputs("内存池创建失败\n", stderr);
        return 1;
    }

    for (i = 0U; i < BLOCK_COUNT; ++i) {
        values[i] = (int*)pool_alloc(pool);
        if (values[i] == NULL) {
            fputs("内存池分配失败\n", stderr);
            pool_destroy(pool);
            return 1;
        }
        *values[i] = (int)(i + 1U) * 10;
    }

    printf("当前数值:%d %d %d %d\n", *values[0], *values[1], *values[2],
           *values[3]);

    if (pool_alloc(pool) == NULL) {
        puts("内存池已满,下一次分配返回 NULL");
    }

    pool_free(pool, values[1]);
    reused_value = (int*)pool_alloc(pool);
    if (reused_value == NULL || reused_value != values[1]) {
        fputs("内存池复用失败\n", stderr);
        pool_destroy(pool);
        return 1;
    }
    *reused_value = 99;
    printf("复用后的数值:%d\n", *reused_value);

    pool_destroy(pool);
    return 0;
}

这两个的实现差不多只是,一个是动态通过malloc创建,一个是静态设置最大的空间进行利用,嵌入式应该用这个合适

@Charliechen114514

Copy link
Copy Markdown
Member

老师,有一个问题,练习3(内存池)固定大小内存池分配器(挑战·可选),实现一个固定大小内存池:从一块大内存里切出固定大小的块,用链表管理空闲块——每个空闲块的前几个字节存指向下一个空闲块的指针这个固定大小内存池是要静态的还是malloc的动态的这两个好像都满足

这个是使用malloc分配内存池

#include <stddef.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

/* 内存块空闲时,块起始位置用于保存指向下一个空闲块的指针。 */
typedef struct PoolBlock {
    struct PoolBlock* next;
} PoolBlock;

typedef struct MemoryPool MemoryPool;

struct MemoryPool {
    unsigned char* storage; // malloc 获得的池底层存储区
    PoolBlock* free_list; // 空闲块链表的头指针
};

/*
 * 创建内存池。
 * block_size 为每个内存块的字节数,block_count 为内存块数量。
 * 创建成功返回内存池指针;参数为 0、大小计算溢出或 malloc 失败时返回 NULL。
 */
MemoryPool* pool_create(size_t block_size, size_t block_count)
{
    MemoryPool* pool;
    size_t alignment;
    size_t block_stride;
    size_t storage_size;
    size_t i;

    if (block_size == 0U || block_count == 0U) {
        return NULL;
    }

    if (block_size < sizeof(PoolBlock)) {
        block_size = sizeof(PoolBlock);
    }
    alignment = _Alignof(max_align_t);
    if (block_size > SIZE_MAX - (alignment - 1U)) {
        return NULL;
    }
    block_stride = ((block_size + alignment - 1U) / alignment) * alignment;
    if (block_count > SIZE_MAX / block_stride) {
        return NULL;
    }
    storage_size = block_stride * block_count;

    pool = malloc(sizeof(*pool));
    if (pool == NULL) {
        return NULL;
    }

    pool->storage = malloc(storage_size);
    if (pool->storage == NULL) {
        free(pool);
        return NULL;
    }

    pool->free_list = NULL;
    for (i = 0U; i < block_count; ++i) {
        PoolBlock* block = (PoolBlock*)(void*)(pool->storage +
                                               i * block_stride);

        block->next = pool->free_list;
        pool->free_list = block;
    }
    return pool;
}

/*
 * 从内存池中分配一个空闲内存块。
 * 分配成功返回内存块首地址;pool 为 NULL 或内存池已满时返回 NULL。
 */
void* pool_alloc(MemoryPool* pool)
{
    PoolBlock* block;

    if (pool == NULL) {
        return NULL;
    }

    block = pool->free_list;
    if (block == NULL) {
        return NULL;
    }

    pool->free_list = block->next;
    return (void*)block;
}

/*
 * 释放由 pool_alloc 从指定内存池分配的内存块。
 * block 必须是该池当前已分配、且尚未释放的块;传入异属地址、块内地址或重复
 * 释放违反接口前置条件,行为未定义。pool 或 block 为 NULL 时函数直接返回。
 */
void pool_free(MemoryPool* pool, void* block)
{
    if (pool == NULL || block == NULL) {
        return;
    }

    ((PoolBlock*)block)->next = pool->free_list;
    pool->free_list = block;
}

/*
 * 销毁内存池。pool 为 NULL 时函数直接返回;调用后该指针失效。
 */
void pool_destroy(MemoryPool* pool)
{
    if (pool == NULL) {
        return;
    }

    free(pool->storage);
    free(pool);
}

int main(void)
{
    enum { BLOCK_COUNT = 4 };
    MemoryPool* pool;
    int* values[BLOCK_COUNT];
    int* reused_value;
    size_t i;

    pool = pool_create(sizeof(int), BLOCK_COUNT);
    if (pool == NULL) {
        fputs("内存池创建失败\n", stderr);
        return 1;
    }

    for (i = 0U; i < BLOCK_COUNT; ++i) {
        values[i] = (int*)pool_alloc(pool);
        if (values[i] == NULL) {
            fputs("内存池分配失败\n", stderr);
            pool_destroy(pool);
            return 1;
        }
        *values[i] = (int)(i + 1U) * 10;
    }

    printf("当前数值:%d %d %d %d\n", *values[0], *values[1], *values[2],
           *values[3]);

    if (pool_alloc(pool) == NULL) {
        puts("内存池已满,下一次分配返回 NULL");
    }

    pool_free(pool, values[1]);
    reused_value = (int*)pool_alloc(pool);
    if (reused_value == NULL || reused_value != values[1]) {
        fputs("内存池复用失败\n", stderr);
        pool_destroy(pool);
        return 1;
    }
    *reused_value = 99;
    printf("复用后的数值:%d\n", *reused_value);

    pool_destroy(pool);
    return 0;
}

这个是静态的

#include <stddef.h>
#include <stdio.h>

/* 单个内存块可容纳的最大字节数。 */
#define MEMORY_POOL_MAX_BLOCK_SIZE 64U
/* 单个内存池可管理的最大内存块数量。 */
#define MEMORY_POOL_MAX_BLOCK_COUNT 16U
/* 程序可同时创建的内存池实例最大数量。 */
#define MEMORY_POOL_MAX_INSTANCES 2U
/* 内存块按照 max_align_t 的对齐要求切分。 */
#define MEMORY_POOL_ALIGNMENT _Alignof(max_align_t)
/* 为每个内存池预留最坏情况下所需的存储空间。 */
#define MEMORY_POOL_MAX_BLOCK_STRIDE                                      \
    (((MEMORY_POOL_MAX_BLOCK_SIZE + MEMORY_POOL_ALIGNMENT - 1U) /         \
      MEMORY_POOL_ALIGNMENT) * MEMORY_POOL_ALIGNMENT)

/* 内存块空闲时,块起始位置用于保存指向下一个空闲块的指针。 */
typedef struct PoolBlock {
    struct PoolBlock* next;
} PoolBlock;

/* 使用联合体确保整块池内存按 max_align_t 对齐。 */
typedef union {
    max_align_t alignment;
    unsigned char bytes[MEMORY_POOL_MAX_BLOCK_STRIDE *
                        MEMORY_POOL_MAX_BLOCK_COUNT];
} PoolStorage;


typedef struct MemoryPool MemoryPool;

struct MemoryPool {
    PoolStorage storage; // 内存池
    PoolBlock* free_list; // 空闲块链表的头指针
    size_t block_stride; // 单个内存块的实际步长(字节)
    size_t block_count; // 当前内存池可分配的最大内存块数量
    int active; // 标记该内存池是否处于启用状态
};

/* 使用静态实例代替运行时堆内存分配。 */
static MemoryPool memory_pools[MEMORY_POOL_MAX_INSTANCES];

/* 判断内存池指针是否指向本模块管理的静态内存池实例。 */
static int pool_is_managed(const MemoryPool* pool)
{
    size_t i;

    for (i = 0U; i < MEMORY_POOL_MAX_INSTANCES; ++i) {
        if (pool == &memory_pools[i]) {
            return 1;
        }
    }
    return 0;
}

/* 查找内存块在指定内存池中的下标;找到时将下标写入 index。 */
static int pool_block_index(const MemoryPool* pool, const void* block,
                            size_t* index)
{
    size_t i;

    if (pool == NULL || block == NULL || index == NULL) {
        return 0;
    }

    for (i = 0U; i < pool->block_count; ++i) {
        const void* current_block =
            (const void*)(pool->storage.bytes + i * pool->block_stride);

        if (block == current_block) {
            *index = i;
            return 1;
        }
    }

    return 0;
}

/* 判断指定内存块是否已经位于空闲链表中。 */
static int pool_block_is_free(const MemoryPool* pool, const PoolBlock* block)
{
    const PoolBlock* current;

    if (pool == NULL || block == NULL) {
        return 0;
    }

    for (current = pool->free_list; current != NULL; current = current->next) {
        if (current == block) {
            return 1;
        }
    }

    return 0;
}

/*
 * 创建内存池。
 * block_size 为每个内存块的字节数,block_count 为内存块数量。
 * 创建成功返回内存池指针;参数超出上限或无空闲实例时返回 NULL。
 */
MemoryPool* pool_create(size_t block_size, size_t block_count)
{
    MemoryPool* pool;
    size_t block_stride;
    size_t i;

    if (block_size == 0U || block_size > MEMORY_POOL_MAX_BLOCK_SIZE ||
        block_count == 0U || block_count > MEMORY_POOL_MAX_BLOCK_COUNT) {
        return NULL;
    }

    block_stride = block_size;
    if (block_stride < sizeof(PoolBlock)) {
        block_stride = sizeof(PoolBlock);
    }
    block_stride = ((block_stride + MEMORY_POOL_ALIGNMENT - 1U) /
                    MEMORY_POOL_ALIGNMENT) * MEMORY_POOL_ALIGNMENT;

    for (i = 0U; i < MEMORY_POOL_MAX_INSTANCES; ++i) {
        pool = &memory_pools[i];
        if (!pool->active) {
            size_t block_index;

            pool->block_stride = block_stride;
            pool->block_count = block_count;
            for (block_index = 0U; block_index < block_count; ++block_index) {
                PoolBlock* current_block = (PoolBlock*)(void*)(
                    pool->storage.bytes + block_index * block_stride);

                if (block_index + 1U < block_count) {
                    current_block->next = (PoolBlock*)(void*)(
                        pool->storage.bytes +
                        (block_index + 1U) * block_stride);
                } else {
                    current_block->next = NULL;
                }
            }
            pool->free_list = (PoolBlock*)(void*)pool->storage.bytes;
            pool->active = 1;
            return pool;
        }
    }

    return NULL;
}

/*
 * 从内存池中分配一个空闲内存块。
 * 分配成功返回内存块首地址;内存池无效或已满时返回 NULL。
 */
void* pool_alloc(MemoryPool* pool)
{
    PoolBlock* block;

    if (!pool_is_managed(pool) || !pool->active) {
        return NULL;
    }

    block = pool->free_list;
    if (block == NULL) {
        return NULL;
    }

    pool->free_list = block->next;
    return (void*)block;
}

/*
 * 释放由 pool_alloc 从指定内存池分配的内存块。
 * pool 或 block 无效、block 不属于该内存池或已释放时,函数直接返回。
 */
void pool_free(MemoryPool* pool, void* block)
{
    size_t index;
    PoolBlock* released_block;

    if (!pool_is_managed(pool) || !pool->active ||
        !pool_block_index(pool, block, &index)) {
        return;
    }

    released_block = (PoolBlock*)(void*)(
        pool->storage.bytes + index * pool->block_stride);
    if (pool_block_is_free(pool, released_block)) {
        return;
    }

    released_block->next = pool->free_list;
    pool->free_list = released_block;
}

/*
 * 销毁内存池并将其静态实例标记为空闲。
 * 内存池无效或已销毁时,函数直接返回。
 */
void pool_destroy(MemoryPool* pool)
{
    if (!pool_is_managed(pool) || !pool->active) {
        return;
    }

    pool->free_list = NULL;
    pool->block_stride = 0U;
    pool->block_count = 0U;
    pool->active = 0;
}

int main(void)
{
    enum { BLOCK_COUNT = 4 };
    MemoryPool* pool;
    int* values[BLOCK_COUNT];
    int* reused_value;
    size_t i;

    pool = pool_create(sizeof(int), BLOCK_COUNT);
    if (pool == NULL) {
        fputs("内存池创建失败\n", stderr);
        return 1;
    }

    for (i = 0U; i < BLOCK_COUNT; ++i) {
        values[i] = (int*)pool_alloc(pool);
        if (values[i] == NULL) {
            fputs("内存池分配失败\n", stderr);
            pool_destroy(pool);
            return 1;
        }
        *values[i] = (int)(i + 1U) * 10;
    }

    printf("当前数值:%d %d %d %d\n", *values[0], *values[1], *values[2],
           *values[3]);

    if (pool_alloc(pool) == NULL) {
        puts("内存池已满,下一次分配返回 NULL");
    }

    pool_free(pool, values[1]);
    reused_value = (int*)pool_alloc(pool);
    if (reused_value == NULL || reused_value != values[1]) {
        fputs("内存池复用失败\n", stderr);
        pool_destroy(pool);
        return 1;
    }
    *reused_value = 99;
    printf("复用后的数值:%d\n", *reused_value);

    pool_destroy(pool);
    return 0;
}

这两个的实现差不多只是,一个是动态通过malloc创建,一个是静态设置最大的空间进行利用,嵌入式应该用这个合适

看到了,可以都写,并且注明比较小的片子推介使用静态内存池。碎片化的确是一个难题。这个没问题的你说的

@Charliechen114514

Copy link
Copy Markdown
Member

以及不用叫我老师啦,我这担不起这个称呼,每一次心惊胆战的hhh

@xiaoshuaijie

Copy link
Copy Markdown
Contributor Author

哈哈,好的好的

@xiaoshuaijie

Copy link
Copy Markdown
Contributor Author

老大改好了,辛苦了

  1. DEBUG_LOG 的展示输出行号与代码不符
    已换成[main.c:的兼容形式
  2. bash 代码块里混进了裸文本
    已经修复
  3. 正文 feof 段落被改出语病
    已经返回原文
  4. 15 号文的编译标准自相矛盾
    已经统一为-std=C17
  5. 练习 3(内存池)的答案没有按题面要求的数据结构做
    已经改为静态与动态两种,不再使用in_use[]数组递归方法
  6. 练习 1(配置解析)答案的函数签名和题面不一致 已修改为size_t
  7. intvec 答案不自包含 已经写到了同一个文件
  8. count_digits 里藏着 printf已经修正
  9. 练习 4(tracked)的文件标注错位 已经修正
  10. ch00/03 的补充超出了读者阶段 改为加了一个拓展(其实是为了我复习时忘记)
  11. 14 号文两段讲解文字需要重写已经重写
  12. tracked_malloc 里往 stdout 打印分配大小 已经删除同时示例改为有一个未释放,同时加了-DNDEBUG的说明

@Charliechen114514

Copy link
Copy Markdown
Member

OK,我这边最后看完了一些小问题,过了CI就可以合入咯!

@Charliechen114514

Copy link
Copy Markdown
Member

哈哈哈哈老大没绷住,叫我bro就好(🐱)

@Charliechen114514

Copy link
Copy Markdown
Member

Approve,感谢大佬!辛苦了!

@Charliechen114514
Charliechen114514 merged commit 2604448 into Awesome-Embedded-Learning-Studio:main Aug 27, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants