Python调用C函数返回未知大小int数组报访问违例如何解决
问题根因
你遇到的OSError: exception: access violation writing 0x0000000000000004错误直接原因是C代码中定义的int* path是未初始化的野指针,没有为其分配有效内存就直接执行path[i] = xxx的写入操作,触发了非法内存访问。
除此之外,直接返回int*类型裸指针的设计本身就存在缺陷:Python侧拿到指针后无法获知指向的数组长度,根本没法正确读取数组内容,同时还会面临内存泄漏、野指针访问的风险。
实现方案
因为无法提前获知返回数组的长度,最稳妥的实现方式是让C函数在返回数组指针的同时,通过出参回传数组的实际长度,所有堆内存申请/释放逻辑在C侧完成,Python侧只负责读取数据和调用释放接口。
C侧代码修改
- 为
path指针通过malloc在堆上分配足够长度的内存,禁止使用未初始化的野指针、禁止返回栈上分配的局部数组地址 - 新增int类型指针出参,用于向调用方回传返回数组的实际长度
- 配套实现数组内存释放函数,供Python侧调用以避免内存泄漏
修改后的C代码示例:
#include <stdlib.h> // 最后一个参数为出参,用于返回数组实际长度 int* prochepoints_tabu(Client* points, int size, int distmax, int* return_len) { // 原有业务逻辑,计算得到路径总长度nsize、路径结果res int nsize = /* 替换为你原有逻辑中计算得到的路径节点总数 */; // 在堆上分配对应长度的int数组内存 int* path = (int*)malloc(sizeof(int) * nsize); if (path == NULL) { *return_len = 0; return NULL; } // 注意循环下标从0开始,原代码从1开始会漏掉首元素且造成越界写入 for (int i = 0; i < nsize; i++) { path[i] = res[i].number; } *return_len = nsize; return path; } // 配套的内存释放函数 void free_int_array(int* arr) { free(arr); }
Python侧调用代码修改
- 配置C函数的参数类型、返回值类型时,增加长度出参的类型定义
- 调用函数时传入c_int类型变量的指针,用于接收C侧回传的数组长度
- 拿到数组指针后,通过切片语法直接读取指定长度的内容转为Python原生列表
- 数据读取完成后调用C侧的释放函数回收内存
修改后的Python代码示例:
from ctypes import * class CA(Structure): _fields_ = [ ('number', c_int), ('x', c_int), ('y', c_int) ] # 加载动态库 dll = CDLL('./functions.so') # 配置函数签名 dll.prochepoints_tabu.argtypes = [POINTER(CA), c_int, c_int, POINTER(c_int)] dll.prochepoints_tabu.restype = POINTER(c_int) # 配置内存释放函数签名 dll.free_int_array.argtypes = [POINTER(c_int)] dll.free_int_array.restype = None # 原有构造CA数组的逻辑保持不变 ca_list = [] newlist = nodes.copy() newlist.pop('0') for key, el in newlist.items(): ca = CA() ca.number = int(key) ca.x = el[0] ca.y = el[1] ca_list.append(ca) ca_array = (CA * len(ca_list))(*ca_list) # 定义变量接收返回的数组长度 path_len = c_int(0) # 调用C函数,最后一个参数传入长度变量的指针 path_ptr = dll.prochepoints_tabu( ca_array, len(ca_array), c_int(vehicles[1]['charged']), byref(path_len) ) # 将C指针指向的数组转为Python原生列表 path_list = path_ptr[:path_len.value] # 释放C侧申请的内存,避免内存泄漏 dll.free_int_array(path_ptr) # 后续可直接使用path_list,为普通Python int列表
注意事项
- 禁止在C函数中返回栈上分配的局部数组(例如
int path[100];),函数退出后栈内存会被系统回收,Python侧拿到的是野指针,会出现随机值或崩溃问题 - 跨动态库的内存申请和释放要保持在同一侧,不要尝试在Python侧直接释放C侧malloc申请的内存,避免内存分配器不兼容导致的崩溃
- 数组循环写入时下标要从0开始,避免越界写入造成的内存破坏
内容的提问来源于stack exchange,提问作者VoidAlpha
相关产品推荐
相关产品推荐

