在 Python 中使用 C 代码返回奇怪的值

问题描述 投票:0回答:1

我在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 c ctypes
1个回答
1
投票

清单 [Python.Docs]:ctypes - Python 的外部函数库.

代码中有一堆错误:

  • Showstopper:SegFaultAccess 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.
© www.soinside.com 2019 - 2024. All rights reserved.