我在C中写了一个函数来获取n以下的所有质数,并用ctypes导入到Python中。 然而,该函数没有返回我所期望的。 Python中如何知道返回的
int*
指针的大小:
primes.c文件:它返回一个
int*
指针,指向n
下的所有质数
int* primes(int n)
{
int i, j;
int *primes = NULL;
int *prime_numbers = NULL;
int num_primes = 0;
if (n < 3) {
printf("n must be >= 3, you input n: %d\n", n);
return NULL;
}
primes = (int *)calloc(n, sizeof(int));
if (primes == NULL) {
printf("Memory allocation failed\n");
return NULL;
}
for (i = 0; i < n; i++) {
primes[i] = 1;
}
primes[0] = 0;
primes[1] = 0;
for (i = 2; i < n; i++) {
if (primes[i]) {
for (j = i*2; j < n; j += i) {
primes[j] = 0;
}
num_primes++;
}
}
j = 0;
prime_numbers = (int *)calloc(num_primes, sizeof(int));
if (prime_numbers == NULL) {
printf("Memory allocation failed\n");
return NULL;
}
for (i = 0; i < n; i++) {
if (primes[i]) {
prime_numbers[j] = i;
j++;
}
}
free(primes);
return prime_numbers;
}
在 Python 中:
import ctypes
from time import perf_counter
library = ctypes.CDLL('./primes.so')
library.primes.argtypes = [ctypes.c_int]
library.primes.restype = ctypes.POINTER(ctypes.c_int)
libc = ctypes.CDLL("libc.so.6")
# ---------------------------------------------------------------------------- #
# --- Primes ----------------------------------------------------------------- #
# ---------------------------------------------------------------------------- #
def primes_c(n: int) -> list[int]:
assert isinstance(n, int), "n must be an integer."
assert (n >= 3), "n must be >= 3."
primes: list[int] = library.primes(n)
return primes
def main():
n: int = 10
print(f"Getting prime numbers under {n:_}.")
print("C implementation:")
start = perf_counter()
prime_numbers_c = primes_c(n)
end = perf_counter()
print(f"took {end - start:.2f} seconds.")
for i in prime_numbers_c:
print(i)
libc.free(prime_numbers_c)
if __name__ == '__main__':
main()
我的输出看起来像这样,段错误
.
.
.
0
0
0
0
0
0
0
[1] 28416 segmentation fault (core dumped) python3 primes.py
清单 [Python.Docs]:ctypes - Python 的外部函数库.
代码中有一堆错误:
Showstopper:SegFault(Access Violation)。你从 C 返回一个指针,但在 Python 中你超越了它的边界(当从它创建列表时),访问不属于“你的”的内存(Undefined Behavior)
免费:
在没有“准备”的情况下调用它。检查 [SO]:通过 ctypes 从 Python 调用的 C 函数返回不正确的值(@CristiFati 的回答) 使用 CTypes(调用函数)时的常见陷阱
一个微妙的:你正在跨越 .dll 边界。在一侧(.dll)分配一个指针并在另一侧(app)释放它会产生UB,如果这两个是使用不同的C运行时构建的(C运行时静态链接在其中一个他们)。 在 .dll 中分配内存时,还提供一个释放它的函数
如果 2nd 数组分配失败则内存泄漏
一些次要的(包括使用名称 primes 来表示 2 个不同的事物)
现在,为了修复(主要)错误,您必须告诉 primes 函数调用者返回的内存块有多大(有关此主题的更多详细信息,请查看 [SO]:Pickling a ctypes.Structure with ct .Pointer(@CristiFati 的回答))。有很多方法可以做到这一点:
在返回块的末尾添加一个 (NULL) 哨兵(如@interjay 所指)
向将接收素数的函数添加一个(输出)参数
将指针及其大小封装在一个结构体中。虽然需要写更多的代码,但我更喜欢那个
我准备了一个例子。
primes.c:
#include <stdio.h>
#include <stdlib.h>
#if defined(_WIN32)
# define DLL00_EXPORT_API __declspec(dllexport)
#else
# define DLL00_EXPORT_API
#endif
typedef unsigned int uint;
typedef struct Buffer_ {
uint count;
uint *data;
} Buffer, *PBuffer;
#if defined(__cplusplus)
extern "C" {
#endif
DLL00_EXPORT_API PBuffer primesEratosthenes(uint n);
DLL00_EXPORT_API void freePtr(PBuffer buf);
#if defined(__cplusplus)
}
#endif
PBuffer primesEratosthenes(uint n)
{
uint i = 0, j = 0, count = 0;
uint *sieve = NULL, *primes = NULL;
PBuffer ret = NULL;
if (n < 3) {
printf("C - n must be >= 3, you input n: %u\n", n);
return NULL;
}
sieve = malloc(n * sizeof(uint));
if (sieve == NULL) {
printf("C - Memory allocation failed 0\n");
return NULL;
}
sieve[0] = 0;
sieve[1] = 0;
for (i = 2; i < n; ++i) {
sieve[i] = 1;
}
for (i = 2; i < n; ++i) {
if (sieve[i]) {
for (j = i * 2; j < n; j += i) {
sieve[j] = 0;
}
++count;
}
}
primes = malloc(count * sizeof(uint));
if (primes == NULL) {
printf("C - Memory allocation failed 1\n");
free(sieve);
return NULL;
}
ret = malloc(sizeof(Buffer));
if (ret == NULL) {
printf("C - Memory allocation failed 2\n");
free(primes);
free(sieve);
return NULL;
}
for (i = 2, j = 0; i < n; ++i) {
if (sieve[i]) {
primes[j] = i;
++j;
}
}
free(sieve);
ret->count = count;
ret->data = primes;
return ret;
}
void freePtr(PBuffer buf)
{
if (buf == NULL) {
return;
}
free(buf->data);
free(buf);
}
code00.py:
#!/usr/bin/env python
import ctypes as cts
import sys
from time import perf_counter
DLL_NAME = "./primes.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")
UIntPtr = cts.POINTER(cts.c_uint)
class Buffer(cts.Structure):
_fields_ = (
("count", cts.c_uint),
("data", UIntPtr),
)
BufferPtr = cts.POINTER(Buffer)
def main(*argv):
dll = cts.CDLL(DLL_NAME)
primes_eratosthenes = dll.primesEratosthenes
primes_eratosthenes.argtypes = (cts.c_int,)
primes_eratosthenes.restype = BufferPtr
free_ptr = dll.freePtr
free_ptr.argtypes = (BufferPtr,)
ns = (
100,
50000000,
)
funcs = (
primes_eratosthenes,
)
for n in ns:
print(f"Searching for primes til {n:d}")
for func in funcs:
start = perf_counter()
pbuf = func(n)
buf = pbuf.contents
if not buf:
print("NULL ptr")
continue
primes = buf.data[:buf.count]
print(f"\n Func {func.__name__} took {perf_counter() - start:.3f} seconds")
print(f" Found {buf.count:d} primes")
if buf.count <= 100:
print(end=" ")
for i in primes:
print(i, end=" ")
print("\n")
free_ptr(pbuf)
if __name__ == "__main__":
print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.\n")
sys.exit(rc)
输出:
(py_pc064_03.10_test0) [cfati@cfati-5510-0:/mnt/e/Work/Dev/StackExchange/StackOverflow/q076283276]> ~/sopr.sh ### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ### [064bit prompt]> [064bit prompt]> ls code00.py primes.c [064bit prompt]> [064bit prompt]> gcc -fPIC -shared -o primes.so primes.c [064bit prompt]> [064bit prompt]> ls code00.py primes.c primes.so [064bit prompt]> [064bit prompt]> python ./code00.py Python 3.10.11 (main, Apr 5 2023, 14:15:10) [GCC 9.4.0] 064bit on linux Searching for primes til 100 Func primesEratosthenesOriginal took 0.000 seconds Found 25 primes 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 Searching for primes til 50000000 Func primesEratosthenesOriginal took 2.301 seconds Found 3001134 primes Done.