You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言链式数组操作的内存管理方法探究

手动内存管理下的C语言数组库内存泄漏问题

背景

刚接触C语言,此前使用Python的numpy时语法十分便捷:

import numpy as np

b = np.array([1,2,3,4,5])
a = np.zeros(b.size)

c = np.sum(np.dot(a,b), np.add(a,b))

因此尝试在C语言中实现一个类似numpy的库,用于学习C语言及配合数值方法课程,目前已完成基础的数组结构与操作函数。

已实现的基础代码

数组结构与构造/析构函数

/*
array type with size and data
*/
typedef struct array
{
    size_t size;
    float data[];
} array;

/*
array constructor
*/
array *array_init(size_t size, float *to_arr)
{
    array *arr = malloc(
        sizeof(array) + sizeof(float) * size
    );

    if (arr==NULL){
        printf("Couldn't allocate memory");
    }

    if (to_arr!=NULL)
    {
        for (size_t i = 0; i < size; i++)
        {
            arr->data[i]=to_arr[i];
        }   
    }
    
    arr->size=size;
    return arr;
}

/*
array constructor
filled with a float n
*/
array *array_like(size_t size, float n)
{
    array *arr = malloc(
        sizeof(array) + sizeof(float) * size
    );

    if (arr==NULL){
        printf("Couldn't allocate memory");
    }

    for (size_t i = 0; i < size; i++)
    {
        arr->data[i]=n;
    }   
    
    arr->size=size;
    return arr;
}


/*
array destructor
*/
void array_del(array *arr)
{
    free(arr);
}

元素级乘法函数

/*
element-wise array multiplication
*/
array *a_mul(array *a, array *b)
{
    array *tmp = array_init(a->size, NULL);

    for (int i = 0; i < a->size; i++)
    {
        tmp->data[i] = a->data[i] * b->data[i];
    }

    return tmp;
    
}

遇到的问题

链式调用时会出现内存泄漏,因为中间返回的临时数组指针超出作用域后无法被释放:

array *a = array_init(3, (float[]){1.0,2.0,3.0});
array *b = array_init(3, (float[]){1.0,2.0,3.0});

array *result = a_mul(a_mul(a_mul(a,b),b),a);

每次a_mul返回的临时数组没有被保存,无法调用array_del释放,导致内存泄漏。

尝试方案与疑问

曾尝试在操作函数中直接释放输入参数:

/*
element-wise array multiplication
*/
array *a_mul(array *a, array *b)
{
    array *tmp = array_init(a->size, NULL);

    for (int i = 0; i < a->size; i++)
    {
        tmp->data[i] = a->data[i] * b->data[i];
    }
    
    free(a);
    free(b);
    return tmp;
    
}

但这种方式会提前释放原数组,若后续需要使用原数组会引发错误。另外了解到内存池(arena)、动态栈的概念,但不确定哪种更适合当前场景。希望明确:

  • 如何解决链式调用的内存泄漏问题?
  • 有哪些相关的最佳实践?
  • 是否必须接受C语言代码比Python繁琐很多?

解决方案与最佳实践

1. 显式管理临时变量(最直接的方案)

放弃链式调用,拆分步骤并手动释放临时数组:

array *a = array_init(3, (float[]){1.0,2.0,3.0});
array *b = array_init(3, (float[]){1.0,2.0,3.0});

array *tmp1 = a_mul(a, b);
array *tmp2 = a_mul(tmp1, b);
array *result = a_mul(tmp2, a);

array_del(tmp1);
array_del(tmp2);
array_del(a);
array_del(b);

这种方式逻辑清晰,完全由开发者掌控内存生命周期,适合学习阶段或小型项目。

2. 提供原地(in-place)操作函数

修改操作函数,允许直接在输入数组上修改,避免创建临时数组:

/* 原地元素级乘法,将结果存入a */
void a_mul_inplace(array *a, const array *b)
{
    for (size_t i = 0; i < a->size; i++)
    {
        a->data[i] *= b->data[i];
    }
}

使用示例:

array *a = array_init(3, (float[]){1.0,2.0,3.0});
array *b = array_init(3, (float[]){1.0,2.0,3.0});
array *result = array_init(3, a->data); // 复制a的初始值

a_mul_inplace(result, b);
a_mul_inplace(result, b);
a_mul_inplace(result, a);

array_del(a);
array_del(b);
// 最后释放result

这种方式减少内存分配次数,避免临时数组泄漏,但需要注意输入数组的生命周期。

3. 使用内存池(Arena Allocation)

实现一个内存池,所有数组都从池中分配,最后一次性释放整个池,无需逐个释放数组:

typedef struct arena {
    void *buf;
    size_t offset;
    size_t total_size;
} arena;

arena *arena_create(size_t total_size) {
    arena *a = malloc(sizeof(arena));
    if (!a) return NULL;
    a->buf = malloc(total_size);
    if (!a->buf) {
        free(a);
        return NULL;
    }
    a->offset = 0;
    a->total_size = total_size;
    return a;
}

void arena_destroy(arena *a) {
    free(a->buf);
    free(a);
}

// 修改array_init,从内存池分配
array *array_init_arena(arena *a, size_t size, float *to_arr) {
    size_t needed = sizeof(array) + sizeof(float) * size;
    if (a->offset + needed > a->total_size) {
        printf("Arena out of memory");
        return NULL;
    }
    array *arr = (array*)((char*)a->buf + a->offset);
    a->offset += needed;
    
    if (to_arr != NULL) {
        for (size_t i = 0; i < size; i++) {
            arr->data[i] = to_arr[i];
        }
    }
    arr->size = size;
    return arr;
}

使用示例:

arena *my_arena = arena_create(1024 * 1024); // 1MB内存池
array *a = array_init_arena(my_arena, 3, (float[]){1.0,2.0,3.0});
array *b = array_init_arena(my_arena, 3, (float[]){1.0,2.0,3.0});

array *result = a_mul(a_mul(a_mul(a,b),b),a);

// 最后只需要销毁整个内存池,所有数组都被释放
arena_destroy(my_arena);

这种方式适合批量创建临时数组的场景,简化内存管理,但需要提前预估内存需求,且无法单独释放某个数组。

4. 引用计数(复杂但更灵活)

给数组结构添加引用计数字段,当引用计数为0时自动释放:

typedef struct array
{
    size_t size;
    size_t ref_count;
    float data[];
} array;

// 创建数组时初始化引用计数为1
array *array_init(size_t size, float *to_arr) {
    array *arr = malloc(sizeof(array) + sizeof(float)*size);
    if (!arr) {
        printf("Couldn't allocate memory");
        return NULL;
    }
    arr->ref_count = 1;
    // ... 其他初始化逻辑
}

// 增加引用计数
void array_retain(array *arr) {
    if (arr) arr->ref_count++;
}

// 减少引用计数,为0时释放
void array_release(array *arr) {
    if (!arr) return;
    arr->ref_count--;
    if (arr->ref_count == 0) {
        free(arr);
    }
}

修改操作函数,管理输入数组的引用计数:

array *a_mul(array *a, array *b) {
    array_retain(a);
    array_retain(b);
    
    array *tmp = array_init(a->size, NULL);
    // ... 计算逻辑
    
    array_release(a);
    array_release(b);
    return tmp;
}

链式调用示例:

array *a = array_init(3, (float[]){1.0,2.0,3.0});
array *b = array_init(3, (float[]){1.0,2.0,3.0});

array *result = a_mul(a_mul(a_mul(a,b),b),a);

array_release(a);
array_release(b);
array_release(result);

这种方式接近Python的内存管理逻辑,但实现复杂度较高,需要小心处理引用计数的增减。


关于C语言代码的繁琐性

是的,C语言作为底层语言,内存管理需要开发者手动掌控,这是其特性而非缺陷——它带来了更高的性能和内存控制能力,但也增加了代码的复杂度。如果追求Python那样的便捷性,C确实会显得繁琐,但这也是学习C语言内存模型的必经过程。

内容的提问来源于stack exchange,提问作者Alberto Méndez-Dettmer Muñoz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 13:32:34