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

相关推荐
铉铉这波能秀7 分钟前
LeetCode Hot100数据结构背景知识之集合(Set)Python2026新版
数据结构·python·算法·leetcode·哈希算法
踢足球092910 分钟前
寒假打卡:2026-2-8
数据结构·算法
老赵说32 分钟前
Java基础数据结构全面解析与实战指南:从小白到高手的通关秘籍
数据结构
铉铉这波能秀42 分钟前
LeetCode Hot100数据结构背景知识之元组(Tuple)Python2026新版
数据结构·python·算法·leetcode·元组·tuple
静听山水1 小时前
Redis核心数据结构-ZSet
数据结构·redis
铉铉这波能秀2 小时前
LeetCode Hot100数据结构背景知识之字典(Dictionary)Python2026新版
数据结构·python·算法·leetcode·字典·dictionary
Queenie_Charlie2 小时前
stars(树状数组)
数据结构·c++·树状数组
静听山水2 小时前
Redis核心数据结构-Set
数据结构·数据库·redis
独好紫罗兰3 小时前
对python的再认识-基于数据结构进行-a005-元组-CRUD
开发语言·数据结构·python
wengqidaifeng3 小时前
数据结构(三)栈和队列(上)栈:计算机世界的“叠叠乐”
c语言·数据结构·数据库·链表