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

C语言中实现内存分配无关的array_slice函数的方案问询

实现与内存分配策略无关的array_slice函数

我们需要实现一个array_slice函数,它能返回数组片段,同时兼容Boehm垃圾回收、引用计数、内存池、标准alloc/free等多种内存分配策略,且实现方式要符合行业惯例、不刻意生硬。现有代码里malloc的部分需要替换成和输入数组old一致的分配方式,目前已经考虑过让数组结构体携带函数指针及相关信息、使用_Generic两种思路,下面给出更多可行方案及细节:

上下文代码

typedef struct array array;
struct array {
    size_t length;
    uintptr_t* thing;
};
array array_slice(array old, int offset)
{
    size_t new_length = old.length - offset;
    array new = {
        .length = new_length,
        // TODO: 这里需要使用和参数"old"相同的分配策略
        .thing = malloc(sizeof(uintptr_t) * new_length)
    };
    size_t j = 0;
    for (size_t i = offset; i < old.length; i++) {
        new.thing[j] = old.thing[i];
        j++;
    }
    return new;
}

可行方案建议

1. 为数组结构体绑定分配元数据(最常用的常规方案)

这是C语言里处理多态资源管理的标准做法,扩展数组结构体,把分配策略相关的信息封装进去:

// 定义分配器结构体,包含分配、释放函数和上下文
typedef struct array_allocator {
    void* (*alloc)(size_t size, void* context);
    void (*free)(void* ptr, void* context);
    void* context; // 可以是内存池指针、引用计数控制块等
} array_allocator;

// 扩展后的数组结构体
typedef struct array array;
struct array {
    size_t length;
    uintptr_t* thing;
    array_allocator allocator; // 携带当前数组的分配策略信息
};

这样array_slice就能直接复用原数组的分配器来创建新数组:

array array_slice(array old, int offset)
{
    size_t new_length = old.length - offset;
    array new = {
        .length = new_length,
        .allocator = old.allocator,
        .thing = old.allocator.alloc(sizeof(uintptr_t) * new_length, old.allocator.context)
    };
    // 元素拷贝逻辑不变
    size_t j = 0;
    for (size_t i = offset; i < old.length; i++) {
        new.thing[j] = old.thing[i];
        j++;
    }
    // 如果是引用计数策略,这里可以按需增加新数组的引用计数
    // 比如判断allocator.context的类型后执行对应操作
    return new;
}

这种方式把分配策略和数组本身绑定,逻辑清晰,维护性强。

2. 编译期多态(_Generic)

如果分配策略在编译阶段就能确定,可以用_Generic实现编译期的分支选择,避免运行时函数指针开销:
首先为数组添加类型标记和对应上下文的联合体:

typedef enum {
    ARRAY_MALLOC,
    ARRAY_REFCOUNT,
    ARRAY_MEMPOOL,
    ARRAY_BOEHM
} array_type;

typedef struct array array;
struct array {
    size_t length;
    uintptr_t* thing;
    array_type type;
    union {
        void* pool;     // 内存池指针
        int* refcount;  // 引用计数指针
    } u;
};

然后用_Generic封装分配逻辑的选择:

#define array_alloc(arr, size) _Generic((arr), \
    array: _array_alloc((arr), (size)) \
)

void* _array_alloc(array old, size_t size) {
    switch (old.type) {
        case ARRAY_MALLOC:
            return malloc(size);
        case ARRAY_REFCOUNT: {
            void* ptr = malloc(size);
            // 假设引用计数和内存块关联,这里增加计数
            *(old.u.refcount) += 1;
            return ptr;
        }
        case ARRAY_MEMPOOL:
            return mempool_alloc(old.u.pool, size);
        case ARRAY_BOEHM:
            return GC_MALLOC(size);
        default:
            return NULL;
    }
}

在array_slice里直接调用array_alloc(old, sizeof(uintptr_t)*new_length)即可,这种方式性能略高,但仅适用于编译期就能确定分配策略的场景。

3. 分配器作为函数参数传入

如果不想修改原数组结构体,可以把分配函数和上下文作为额外参数传给array_slice:

// 定义分配函数的类型
typedef void* (*array_alloc_func)(size_t size, void* context);

array array_slice(array old, int offset, array_alloc_func alloc, void* context)
{
    size_t new_length = old.length - offset;
    array new = {
        .length = new_length,
        .thing = alloc(sizeof(uintptr_t) * new_length, context)
    };
    // 元素拷贝逻辑不变
    size_t j = 0;
    for (size_t i = offset; i < old.length; i++) {
        new.thing[j] = old.thing[i];
        j++;
    }
    return new;
}

调用时根据原数组的分配策略传入对应的函数和上下文:

// 标准malloc方式调用
array sliced = array_slice(old_arr, 2, malloc, NULL);
// 内存池方式调用
array sliced_pool = array_slice(old_arr, 2, mempool_alloc, my_pool);

这种方式灵活性最高,但调用时需要额外传递参数,略显繁琐。

4. 引用计数策略的特殊优化

如果数组元素是引用类型,引用计数场景下可以采用**写时复制(Copy-On-Write)**的思路,避免不必要的内存拷贝:

// 针对引用计数的数组结构体
typedef struct refcount_array {
    size_t length;
    uintptr_t* thing;
    int* refcount;
    size_t start; // 记录切片的起始偏移
} refcount_array;

切片时不需要拷贝底层内存,只需要创建新的数组结构体,共享原内存并增加引用计数:

refcount_array refcount_array_slice(refcount_array old, int offset) {
    refcount_array new = {
        .length = old.length - offset,
        .thing = old.thing,
        .refcount = old.refcount,
        .start = old.start + offset
    };
    // 增加引用计数
    *(new.refcount) += 1;
    return new;
}

只有当需要修改切片数组的元素时,才真正拷贝底层内存并创建独立的引用计数。这种方式能大幅减少内存开销和拷贝时间,但仅适用于引用计数策略,需要和其他策略分开处理。


内容的提问来源于stack exchange,提问作者Olle Härstedt

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 04:50:26