Python调用C素数函数返回异常,如何获取int*指针长度?
解决ctypes调用C素数函数的段错误问题及获取返回数组大小的方法
你的核心问题是C函数只返回了素数数组的指针,但Python无法知道这个数组的元素个数,遍历的时候会越界访问不属于数组的内存,最终触发段错误。下面给出几种可行的解决方案:
方案1:修改C函数,返回包含数组指针和长度的结构体
通过定义结构体同时存储素数数组指针和素数个数,Python用ctypes解析结构体获取完整信息。
修改后的C代码(primes.c)
#include <stdio.h> #include <stdlib.h> // 定义存储结果的结构体 typedef struct { int* primes; int count; } PrimeResult; PrimeResult primes(int n) { int i, j; int *sieve = NULL; int *prime_numbers = NULL; int num_primes = 0; PrimeResult result = {NULL, 0}; if (n < 3) { printf("n must be >= 3, you input n: %d\n", n); return result; } sieve = (int *)calloc(n, sizeof(int)); if (sieve == NULL) { printf("Memory allocation failed\n"); return result; } for (i = 0; i < n; i++) { sieve[i] = 1; } sieve[0] = 0; sieve[1] = 0; for (i = 2; i < n; i++) { if (sieve[i]) { for (j = i*2; j < n; j += i) { sieve[j] = 0; } num_primes++; } } prime_numbers = (int *)calloc(num_primes, sizeof(int)); if (prime_numbers == NULL) { printf("Memory allocation failed\n"); free(sieve); return result; } j = 0; for (i = 0; i < n; i++) { if (sieve[i]) { prime_numbers[j] = i; j++; } } free(sieve); result.primes = prime_numbers; result.count = num_primes; return result; }
修改后的Python调用代码
import ctypes from time import perf_counter # 定义对应C的结构体 class PrimeResult(ctypes.Structure): _fields_ = [("primes", ctypes.POINTER(ctypes.c_int)), ("count", ctypes.c_int)] library = ctypes.CDLL('./primes.so') library.primes.argtypes = [ctypes.c_int] library.primes.restype = PrimeResult libc = ctypes.CDLL("libc.so.6") def primes_c(n: int) -> tuple[list[int], ctypes.POINTER(ctypes.c_int)]: assert isinstance(n, int), "n must be an integer." assert (n >= 3), "n must be >= 3." result = library.primes(n) # 根据count把指针转成Python列表 prime_list = [result.primes[i] for i in range(result.count)] return prime_list, result.primes def main(): n: int = 10 print(f"获取{n}以内的素数:") print("C实现:") start = perf_counter() prime_numbers_c, ptr = primes_c(n) end = perf_counter() print(f"耗时 {end - start:.2f} 秒.") for num in prime_numbers_c: print(num) # 释放C分配的内存 libc.free(ptr) if __name__ == '__main__': main()
方案2:让C函数返回素数个数,数组指针通过输出参数传递
无需定义结构体,用指针参数传递数组地址,返回值表示素数数量。
修改后的C代码(primes.c)
#include <stdio.h> #include <stdlib.h> int primes(int n, int** out_primes) { int i, j; int *sieve = NULL; int *prime_numbers = NULL; int num_primes = 0; if (n < 3) { printf("n must be >= 3, you input n: %d\n", n); return 0; } if (out_primes == NULL) { printf("Invalid output pointer\n"); return 0; } sieve = (int *)calloc(n, sizeof(int)); if (sieve == NULL) { printf("Memory allocation failed\n"); return 0; } for (i = 0; i < n; i++) { sieve[i] = 1; } sieve[0] = 0; sieve[1] = 0; for (i = 2; i < n; i++) { if (sieve[i]) { for (j = i*2; j < n; j += i) { sieve[j] = 0; } num_primes++; } } prime_numbers = (int *)calloc(num_primes, sizeof(int)); if (prime_numbers == NULL) { printf("Memory allocation failed\n"); free(sieve); return 0; } j = 0; for (i = 0; i < n; i++) { if (sieve[i]) { prime_numbers[j] = i; j++; } } free(sieve); *out_primes = prime_numbers; return num_primes; }
修改后的Python调用代码
import ctypes from time import perf_counter library = ctypes.CDLL('./primes.so') # 第二个参数是int**类型,用POINTER(POINTER(c_int)) library.primes.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.POINTER(ctypes.c_int))] library.primes.restype = ctypes.c_int libc = ctypes.CDLL("libc.so.6") def primes_c(n: int) -> tuple[list[int], ctypes.POINTER(ctypes.c_int)]: assert isinstance(n, int), "n must be an integer." assert (n >= 3), "n must be >= 3." ptr = ctypes.POINTER(ctypes.c_int)() count = library.primes(n, ctypes.byref(ptr)) prime_list = [ptr[i] for i in range(count)] return prime_list, ptr def main(): n: int = 10 print(f"获取{n}以内的素数:") print("C实现:") start = perf_counter() prime_numbers_c, ptr = primes_c(n) end = perf_counter() print(f"耗时 {end - start:.2f} 秒.") for num in prime_numbers_c: print(num) libc.free(ptr) if __name__ == '__main__': main()
方案3:在数组开头存储素数个数(简易实现)
让C函数分配数组时多留一个位置,第一个元素存素数个数,后续存储素数,Python先读取长度再遍历。
修改后的C代码关键片段
// 分配数组时多一个int空间存个数 prime_numbers = (int *)calloc(num_primes + 1, sizeof(int)); if (prime_numbers == NULL) { printf("Memory allocation failed\n"); free(sieve); return NULL; } // 第一个元素存素数数量 prime_numbers[0] = num_primes; j = 1; // 从索引1开始存素数 for (i = 0; i < n; i++) { if (sieve[i]) { prime_numbers[j] = i; j++; } }
Python调用代码修改
def primes_c(n: int) -> tuple[list[int], ctypes.POINTER(ctypes.c_int)]: assert isinstance(n, int), "n must be an integer." assert (n >= 3), "n must be >= 3." ptr = library.primes(n) if not ptr: return [], None count = ptr[0] prime_list = [ptr[i] for i in range(1, count+1)] return prime_list, ptr
原代码段错误原因
原代码中Python拿到int*指针后直接遍历,会从指针位置开始持续读取内存,直到访问到非法区域。而C函数分配的数组只有num_primes个元素,超出范围的内存属于未定义区域,最终触发段错误。
内容的提问来源于stack exchange,提问作者Wihan Uys
相关产品推荐
相关产品推荐

