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

使用ctypes调用C代码时的内存释放与Numpy数组转换问题

解决ctypes调用C函数时的内存释放段错误问题

你遇到的核心问题其实是C函数返回了栈内存的指针,这是C语言里的经典陷阱,咱们一步步拆解:

为什么会触发段错误?

你C代码里的double results[items];是在栈上分配的局部数组——栈内存的生命周期只在函数执行期间,当getStuff函数返回时,栈帧会被销毁,这块内存会被操作系统回收或者重新分配给其他调用。此时你返回的results_p指向的是已经失效的内存区域,哪怕Python里显示的地址和C里一致,这块内存也已经不属于你的程序可以合法操作的范围了。

更关键的是:free函数只能释放通过malloc/calloc/realloc在堆上分配的内存,你用它去释放栈内存,直接就会触发段错误——这完全是错误的内存操作。

修正C代码:改用堆内存分配

要解决这个问题,首先得把数组分配在堆上,这样内存会一直保留到你主动调用free释放:

double* getStuff(double *R_list, int items){
    // 用malloc在堆上分配内存,注意要乘以double的字节数
    double* results = (double*)malloc(items * sizeof(double));
    if (results == NULL) {
        // 内存分配失败的容错处理,返回NULL避免后续崩溃
        fprintf(stderr, "Memory allocation failed!\n");
        return NULL;
    }
    for(int i = 0; i < items; i++){
        double res = /* 你的计算逻辑 */;
        results[i] = res;
    }
    printf("C allocated address %p \n", results);
    return results;
}

void free_mem(double *a){
    printf("freeing address: %p\n", a);
    free(a);
}

重新编译共享库后,这个函数返回的指针就指向合法的堆内存了,后续调用free_mem就不会崩溃。

Python端正确处理:保留Numpy数组并安全释放内存

当你拿到C端返回的堆内存指针后,有两种稳妥的处理方式:

方式1:创建Numpy数组副本,手动释放原内存

这种方式最适合新手,逻辑清晰不容易出错:

from ctypes import *
import numpy as np

mydll = CDLL("libsimps.so")
mydll.getStuff.restype = POINTER(c_double)
mydll.getStuff.argtypes = [POINTER(c_double), c_int]
mydll.free_mem.restype = None
mydll.free_mem.argtypes = [POINTER(c_double)]

R = np.logspace(np.log10(0.011), 1, 100, dtype=np.float64)
tracers = c_int(len(R))
R_c = R.ctypes.data_as(POINTER(c_double))

# 调用C函数获取堆内存指针
c_ptr = mydll.getStuff(R_c, tracers)
print('Python received address', hex(c_ptr))

# 创建Numpy数组的副本——副本会存在Python的内存空间里,和C端指针无关
for_list_py = np.array(np.fromiter(c_ptr, dtype=np.float64, count=len(R)))

# 现在可以安全释放C端分配的堆内存
mydll.free_mem(c_ptr)

这样即使释放了C端的内存,Numpy的副本依然可以正常使用。

方式2:让Numpy接管内存(自动释放)

如果你不想手动调用free,可以让Numpy关联C端内存,利用Python的垃圾回收机制自动释放:

from ctypes import *
import numpy as np

mydll = CDLL("libsimps.so")
mydll.getStuff.restype = POINTER(c_double)
mydll.getStuff.argtypes = [POINTER(c_double), c_int]

# 加载系统的libc库,用来调用free
libc = CDLL("libc.so.6")

R = np.logspace(np.log10(0.011), 1, 100, dtype=np.float64)
tracers = c_int(len(R))
R_c = R.ctypes.data_as(POINTER(c_double))

c_ptr = mydll.getStuff(R_c, tracers)
# 将C指针转换成Numpy数组,指定形状
arr = np.ctypeslib.as_array(c_ptr, shape=(len(R),))
# 把C指针绑定到数组的base属性上,这样数组被回收时会自动调用libc.free
arr.base = c_void_p(c_ptr)

# 之后arr可以正常使用,当它被Python垃圾回收时,内存会自动释放

这种方式省去了手动调用free的步骤,但需要注意不要提前让arr被回收,也不要重复释放内存。

关于你改用SWIG的选择

SWIG确实在处理C/Python交互(尤其是Numpy多维数组)时更省心,它会自动封装很多内存管理和类型转换的细节,减少手动出错的概率,对于复杂场景来说是个很好的选择。

内容的提问来源于stack exchange,提问作者Anna

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 09:16:40