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

相关推荐
203号居民10 小时前
LeetCode hot 100 —41. 缺失的第一个正数
数据结构·算法·leetcode
2401_8628808214 小时前
数据结构 --- 栈
c语言·数据结构·算法
额额额对了16 小时前
数据结构:二叉树
c语言·数据结构·算法
qeen8716 小时前
【数据结构】红黑树的算法原理解析与实现
开发语言·数据结构·c++·算法·红黑树
m0_5474866617 小时前
《数据结构与算法(Python语言版)》全套PPT课件2026
数据结构·算法
hold?fish:palm17 小时前
27 合并两个有序链表
数据结构·链表
个 人 练 习 生17 小时前
数据结构入门:算法复杂度
开发语言·数据结构·经验分享·学习·程序人生·算法
月华路18 小时前
Guava 图数据结构 graph 从基础概念到原理分析
java·数据结构·guava·图搜索
_wxd66620 小时前
排序—插入排序(Insertion Sort)
数据结构
临床数据科学和人工智能兴趣组20 小时前
R语言中,列表是一种非常灵活的数据结构,它可以存储不同类型的对象,如向量、矩阵、数据框、甚至其他列表
数据结构·数据库·r语言·r语言-4.2.1·临床试验