创建链表注意项(三):使用内存池减少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 直接返回的指针,不能是指向该内存块内部的指针。

相关推荐
inrichtt32 分钟前
第十一课时:串口中断接收与回调函数实战解析
c语言·数据结构·stm32·单片机·嵌入式硬件
信奥卷王1 小时前
[GESP202406 六级] 二叉树(含视频)
数据结构·算法
starzy19903 小时前
KafkaProducer 源码深度剖析:支撑高吞吐的数据结构设计
大数据·数据结构·kafka
y1su4 小时前
Leetcode 链表,树,回溯 模板
java·leetcode·链表
罗湖老棍子15 小时前
【例 1】Sightseeing Trip(信息学奥赛一本通- P1494)(洛谷-P10927)
数据结构·算法·图论·最短路·floyd·最小环
郝亚军18 小时前
manifest 中 `sha256` 与 `signValue` 的作用
java·开发语言·数据结构
Nil20819 小时前
LeetCode 283移动零
数据结构·算法·leetcode
库玛西21 小时前
外部排序全解析:败者树、置换-选择排序与最佳归并树
c语言·数据结构·c++·笔记·考研·算法
花间相见1 天前
【计算基础|MySQL08】索引(上):为什么是 B+ 树
数据结构·mysql·算法·架构
夊1 天前
数据结构第二课:顺序表揭秘
数据结构