创建链表注意项(三):使用内存池减少malloc开销(用标志位或空闲链表)

一、减少malloc开销的错误做法

这种说法的讨论:"C语言创建链表节点,如果节点个数 n 已知,可以一次性预分配所有节点(例如,malloc(sizeof(struct node_st) * (n + 1)))以减少碎片和失败点。然后手动链接它们。这更快且更原子化,但需要小心索引。"

假设共有大于n个节点,现在先一次性分配m个节点,m<n,那么以后在使用free函数释放内存的时候,不可以一个节点一个节点的调用free函数释放内存。

如果 n 已知,可以一次性预分配所有节点(例如,malloc(sizeof(struct node_st) * (n + 1)))以减少碎片和失败点。然后手动链接它们。这更快且更原子化,但需要小心索引。

二、原因

这样做会导致未定义行为,很可能造成程序崩溃。

原因

free() 函数只能释放整个 malloc() 返回的内存块,不能释放其中的一部分。

错误示例

c 复制代码
// 一次性分配 m 个节点
struct node_st *nodes = malloc(sizeof(struct node_st) * m);

// ❌ 错误!不能这样释放单个节点
free(&nodes[0]);  // 崩溃或未定义行为
free(&nodes[1]);  // 崩溃或未定义行为

出错分析

  1. 内存管理器的元数据 :malloc 会在分配的内存块前面存储元数据(如块大小),free 依赖这些元数据来释放内存
  2. &nodes[1] 不是 malloc 返回的地址:它指向大块内存的中间位置,那里没有有效的元数据
  3. 破坏堆结构:尝试释放非起始地址会破坏内存管理器的内部数据结构

三、正确的做法

方案 1:只能整体释放

c 复制代码
struct node_st *nodes = malloc(sizeof(struct node_st) * m);

// 使用节点...

// ✓ 正确:只能一次性释放整个块
free(nodes);

方案 2:使用"内存池"管理已用/未用节点

如果需要"逻辑上"释放单个节点,可以用标志位或空闲链表:

c 复制代码
struct node_st {
    int data;
    struct node_st *next;
    int in_use;  // 标记是否在使用
};

struct node_st *nodes = malloc(sizeof(struct node_st) * m);

// 初始化:所有节点标记为未使用
for (int i = 0; i < m; i++) {
    nodes[i].in_use = 0;
}

// "分配"一个节点
struct node_st *alloc_node() {
    for (int i = 0; i < m; i++) {
        if (!nodes[i].in_use) {
            nodes[i].in_use = 1;
            return &nodes[i];
        }
    }
    return NULL;  // 池已满
}

// "释放"一个节点(逻辑释放,不是真正的 free)
void free_node(struct node_st *node) {
    node->in_use = 0;
}

// 最后一次性释放整个池
free(nodes);

方案 3:混合策略

如果需要真正的动态释放,可以结合两种方式:

c 复制代码
// 预分配的节点池(不能单独释放)
struct node_st *pool = malloc(sizeof(struct node_st) * m);
int pool_used = 0;

// 从池中分配
struct node_st *alloc_from_pool() {
    if (pool_used < m) {
        return &pool[pool_used++];
    }
    // 池用完了,单独分配(可以单独释放)
    return malloc(sizeof(struct node_st));
}

// 释放时需要判断是否来自池
void free_node(struct node_st *node) {
    if (node >= pool && node < pool + m) {
        // 来自池,不能释放,可以标记为未使用
        // 或者什么都不做
    } else {
        // 单独分配的,可以释放
        free(node);
    }
}

总结

分配方式 释放方式 能否单独释放
malloc(sizeof(T) * m) free(ptr) ❌ 只能整体释放
循环调用 malloc(sizeof(T)) 循环调用 free ✓ 可以单独释放

记住free()的原则 :free() 的参数必须是 malloc/calloc/realloc 直接返回的指针,不能是指向该内存块内部的指针。

相关推荐
今晚打老虎8 小时前
c++之提高A(前缀和)(第三课)
数据结构·c++·算法
不会就选b9 小时前
算法日常・每日刷题--<贪心>26
数据结构·算法
Logic10112 小时前
C语言/数据结构位运算题解:异或XOR找出书柜中编号只出现一次的那一层——只出现一次的数字
c语言·数据结构·数组·位运算·时间复杂度·算法题·异或性质
青山木13 小时前
Hot 100 --- 下一个排列
java·数据结构·算法·leetcode
SelectDB技术团队15 小时前
Apache Doris 高性能 Open Lake Variant 读写技术解析(含对比数据)
数据结构·数据库·clickhouse·日志·apache doris·selectdb·湖仓架构升级
kiracrimson15 小时前
希尔排序:让插入排序不再怕逆序
数据结构·算法·排序算法
Logic10115 小时前
C语言/数据结构位运算题解:异或XOR找出星球探险中的“独特能量石“——只出现一次的数字
c语言·数据结构·数组·位运算·时间复杂度·算法题·异或性质
hnjzsyjyj18 小时前
洛谷 P1339:[USACO09OCT] Heat Wave G ← Bellman-Ford 算法
数据结构·bellman-ford 算法
SelectDB技术团队19 小时前
ClickHouse 存日志的能力边界:并发、检索与 trace 回放的实测对照
数据结构·数据库·clickhouse·日志分析·apache doris·日志存储
垆边人似月.19 小时前
华为算法题:最长连续递增子序列的变
数据结构·算法·leetcode