docs(c): 补全动态内存、预处理器与文件 I/O 练习答案 - #165
Conversation
|
对于这次的答案,我对于第16章文件 I/O 与标准库概览的理解和运用没有到位,大部分是借助ai完成的,自己写不出来,提交的参考答案我认真阅读过感觉是没有问题,同时我对第15章的环境由-std=C17改为了-std=gnu11,为了拓展typedef这种安全的保护,我在嵌入式开发中常用,同时在第14章的练习较为难理解的部分添加了自己的理解文字说明,辛苦老师了 |
|
现在正式进行C++的学习了,进阶的部分后面有时间再看哈哈 |
|
没事没事,因为这个练习 @owollz4 就吐槽过梯度有点大,这个我的确是按照很熟悉的人复习写的,没有顾虑到。今天刚下班。我稍后审查一下您提交的两个PR |
okok,其实如果是嵌入式开发,进阶先于C++,大部分公司似乎还在用C,哈哈。我也是因为这个顾虑跑去做C-Journey |
|
这个PR我看了一下哈,嗯,好像看起来不太对 必修1. DEBUG_LOG 的展示输出行号与代码不符( 按文中代码实际编译运行,输出是: 上面是我按文中展示块抄的;实际是 这边的建议是不写死,提示一下这个跟代码所在行数有关系比较好。 2. bash 代码块里混进了裸文本( heredoc 写配置文件、「编译运行:」、gcc 命令、运行命令全在同一个 ```bash 块里。「编译运行:」这行会被 shell 当成命令执行,读者整块复制粘贴直接 3. 正文 feof 段落被改出语病( 原句「 4. 15 号文的编译标准自相矛盾( 正文在 main 上只有
也就是说没有一套旗标能既保留这些扩展又保持全仓库统一的零警告。比较推介练习 1 答案去掉题面没要求的 MAX/MIN GNU 宏( 5. 练习 3(内存池)的答案没有按题面要求的数据结构做( 题面写的是「用链表管理空闲块——每个空闲块的前几个字节存指向下一个空闲块的指针」,难度标注也点名「需要自学空闲链表(free list)惯用法」——这是这道题要教的东西。答案用的是静态数组加 6. 练习 1(配置解析)答案的函数签名和题面不一致( 题面是 建议修7. intvec 答案不自包含( 「把上面的 intvec_init / intvec_push / intvec_free 粘贴到此处」——但 8. 函数既计算位数又自己打印,导致输出里夹着孤零零的 9. 练习 4(tracked)的文件标注错位(
10. ch00/03 的补充超出了读者阶段( 这是 hello world 那一篇,读者还没认识指针,就补了 11. 14 号文两段讲解文字需要重写( 「 12.
顺手的小问题
看看我这里搞错啥了没有? |
|
我丢,这么多问题,辛苦老师了,我马上修 |
啊不一定准的,因为一些AI初步扫描的,昨天感觉精力不太好,就先初步审核一下就发上来了,您看看一些是不是误伤, |
|
练习3(内存池)的答案没有按题面要求的数据结构做(14-dynamic-memory.md:388)这个我当时换了,但是好像忘记保存了,不小心用了https://github.com/Awesome-Embedded-Learning-Studio/Tutorial_AwesomeModernCPP/pull/162/changes#diff-387707f91c09fa01a28d8e312425eaeff4a571e6591f44225a53e0be9bc071ec这个旧版了以为这个换回来了 |
OKOK,这个的话,到时候您搞定了让我看看就好,如果感觉这段时间不太方便千万别勉强,可以转draft,别给自己太大压力了,好好休息第一 |
|
老师,有一个问题,练习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创建,一个是静态设置最大的空间进行利用,嵌入式应该用这个合适 |
看到了,可以都写,并且注明比较小的片子推介使用静态内存池。碎片化的确是一个难题。这个没问题的你说的 |
|
以及不用叫我老师啦,我这担不起这个称呼,每一次心惊胆战的hhh |
|
哈哈,好的好的 |
老大改好了,辛苦了
|
|
OK,我这边最后看完了一些小问题,过了CI就可以合入咯! |
|
哈哈哈哈老大没绷住,叫我bro就好(🐱) |
|
Approve,感谢大佬!辛苦了! |
2604448
into
Awesome-Embedded-Learning-Studio:main
补充 C 教程第 14 至 16 章的练习参考答案、可运行示例和验证步骤,覆盖动态内存、预处理器与多文件工程、文件 I/O 三个连续主题