如何在Python中为C结构体动态数组分配内存?
解决ctypes调用C代码时动态结构体数组的内存分配问题
你遇到的ValueError: NULL pointer access本质是C代码中Bar结构体的f指针指向了NULL,C函数遍历数组时访问了无效内存。核心解决思路是在Python侧为结构体数组提前分配内存,或让C侧负责内存分配后在Python侧正确解析并释放内存,以下分两种场景给出具体实现:
场景1:已知结构体数组大小(静态确定)
假设你的C代码如下:
#include <stdio.h> typedef struct { int x; int y; } Foo; typedef struct { Foo* f; int count; } Bar; void iterate_bar(Bar* bar) { for (int i = 0; i < bar->count; i++) { printf("Foo %d: x=%d, y=%d\n", i, bar->f[i].x, bar->f[i].y); } }
Python侧可以直接创建指定大小的结构体数组,再将指针传递给Bar:
import ctypes # 映射C结构体到Python class Foo(ctypes.Structure): _fields_ = [("x", ctypes.c_int), ("y", ctypes.c_int)] class Bar(ctypes.Structure): _fields_ = [("f", ctypes.POINTER(Foo)), ("count", ctypes.c_int)] # 加载编译好的动态库(Windows用.dll,Linux/macOS用.so) lib = ctypes.CDLL("./libtest.so") # 声明函数参数和返回值类型(必须,避免类型转换错误) lib.iterate_bar.argtypes = [ctypes.POINTER(Bar)] lib.iterate_bar.restype = None # 分配3个Foo的数组 count = 3 foo_array = (Foo * count)() # 给数组元素赋值 foo_array[0].x = 10 foo_array[0].y = 20 foo_array[1].x = 30 foo_array[1].y = 40 foo_array[2].x = 50 foo_array[2].y = 60 # 组装Bar结构体并调用C函数 bar = Bar() bar.f = ctypes.cast(foo_array, ctypes.POINTER(Foo)) bar.count = count lib.iterate_bar(ctypes.byref(bar))
场景2:数组大小动态不确定(由C侧分配内存)
如果数组大小需要C函数计算后动态分配,需让C提供内存创建和释放的接口,避免Python侧无法管理C堆内存导致泄漏:
修改后的C代码:
#include <stdio.h> #include <stdlib.h> typedef struct { int x; int y; } Foo; typedef struct { Foo* f; int count; } Bar; // 创建并初始化Bar及内部Foo数组 Bar* create_bar(int count) { Bar* bar = (Bar*)malloc(sizeof(Bar)); bar->count = count; bar->f = (Foo*)malloc(count * sizeof(Foo)); // 示例赋值 for (int i = 0; i < count; i++) { bar->f[i].x = i * 10; bar->f[i].y = i * 20; } return bar; } void iterate_bar(Bar* bar) { for (int i = 0; i < bar->count; i++) { printf("Foo %d: x=%d, y=%d\n", i, bar->f[i].x, bar->f[i].y); } } // 释放C侧分配的内存 void free_bar(Bar* bar) { free(bar->f); free(bar); }
Python侧调用代码:
import ctypes class Foo(ctypes.Structure): _fields_ = [("x", ctypes.c_int), ("y", ctypes.c_int)] class Bar(ctypes.Structure): _fields_ = [("f", ctypes.POINTER(Foo)), ("count", ctypes.c_int)] lib = ctypes.CDLL("./libtest.so") # 声明所有C函数的类型 lib.create_bar.argtypes = [ctypes.c_int] lib.create_bar.restype = ctypes.POINTER(Bar) lib.iterate_bar.argtypes = [ctypes.POINTER(Bar)] lib.iterate_bar.restype = None lib.free_bar.argtypes = [ctypes.POINTER(Bar)] lib.free_bar.restype = None # 调用C函数创建Bar bar_ptr = lib.create_bar(4) # 访问结构体内容 print(f"数组长度: {bar_ptr.contents.count}") for i in range(bar_ptr.contents.count): foo = bar_ptr.contents.f[i] print(f"Foo {i}: x={foo.x}, y={foo.y}") # 调用遍历函数 lib.iterate_bar(bar_ptr) # 必须调用C的释放函数,Python无法回收C堆内存 lib.free_bar(bar_ptr)
关键注意事项
- 必须显式声明C函数的
argtypes和restype,否则ctypes会自动进行不安全的类型转换,引发未知错误。 - Python侧分配的数组,只要对应的Python对象(如
foo_array)未被垃圾回收,内存就有效;若需长期使用,需保证对象存活。 - C侧分配的内存,必须用C提供的释放函数清理,不能用Python的
del,否则会造成内存泄漏。
内容的提问来源于stack exchange,提问作者milanHrabos
相关产品推荐
相关产品推荐

