使用ctypes传递结构体指针

1 投票
1 回答
1108 浏览
提问于 2025-04-18 21:44

到目前为止,我做了一个小的 ctypes 和 Python 代码,具体步骤如下:

  • Python 调用一个 C 语言的函数,并传入一个指向空指针的指针作为参数。

  • C 代码创建了一个名为 ReturnStruct 的结构体,并实例化它和它的数据成员,然后将 Python 传入的指针指向这个结构体。

  • Python 多次调用另一个 C 函数来增加一些值。

  • Python 然后检查这些值。

  • 最后,Python 调用一个 C 函数来释放结构体的指针。

到目前为止,我已经完成了前面三个步骤,但在最后两个部分遇到了问题。以下是 C 代码:

#include <stdio.h>
#include <stdlib.h>
//#include "runSolver.h"

#define SMB_MAX_DATA_SIZE 16

typedef struct testStruct {
  double *x[11];
  double *u[10];
} Test;

typedef struct returnStruct_t {
  Test* vars;
} ReturnStruct;

void initalize_returnStruct(void** returnStruct){
  ReturnStruct* new_returnStruct = (ReturnStruct *)malloc(sizeof(ReturnStruct));
  Test* varsStruct = (Test*)malloc(sizeof(Test)*3);

  int dataSize = 5;
  int i;

  for(i = 0; i < 3; i++){
    int x;
    for(x = 0; x < 11; x++)
      varsStruct[i].x[x] = (double *)malloc(sizeof(double)*5);    
    for(x = 0; x < 10; x++)
      varsStruct[i].u[x] = (double *)malloc(sizeof(double)*5);    
  }
  new_returnStruct->vars = varsStruct;
  *returnStruct = new_returnStruct;
}

void free_returnStruct(void* data){
  ReturnStruct* returnStruct = data;
  int i;
  for(i = 0; i < 3; i++){
    int x;
    for(x = 1; x < 11; x++) 
      free(returnStruct->vars[i].x[x]);
    for(x = 0; x < 10; x++)
      free(returnStruct->vars[i].u[x]);
  }
  free(returnStruct->vars);
  free(returnStruct);
}

void parallelSolver(void* data){

  ReturnStruct* VarsArray = data;

  fprintf(stderr, " This is the value: %f \n", VarsArray->vars[0].x[0][0]);  
  fprintf(stderr, " This is the value: %f \n", VarsArray->vars[0].x[10][4]);
  fprintf(stderr, " This is the value: %f \n", VarsArray->vars[0].u[0][0]);
  fprintf(stderr, " This is the value: %f \n", VarsArray->vars[0].u[9][2]);

  VarsArray->vars[0].x[0][0] += 20.0;
  VarsArray->vars[0].x[10][4] += 203.0;
  VarsArray->vars[0].u[0][0] += 202.0;
  VarsArray->vars[0].u[9][2] += 202.0;                         
}

这是 Python 代码:

#!/usr/bin/python

import sys
import ctypes as ct

numOpt = 3

class vars_t(ct.Structure):
    _fields_ = [("u", ct.POINTER(ct.c_double*10)),
                    ("x", ct.POINTER(ct.c_double*11))]

class returnStruct_t(ct.Structure):
    _fields_ = [("vars", vars_t*numOpt)]

runSolver = ct.CDLL('./runSolverParallel.so')

returnStructPointer = ct.POINTER(returnStruct_t)
runSolver.parallelSolver.argtypes = [ct.c_void_p()]

varsd = ct.c_void_p()
runSolver.initalize_returnStruct(ct.byref(varsd)) 

runSolver.parallelSolver(varsd)
runSolver.parallelSolver(varsd)
runSolver.parallelSolver(varsd)
runSolver.parallelSolver(varsd)

varsdb = ct.cast(varsd, returnStruct_t)

print(varsdb.contents.vars[0].x[0][0])

runSolver.free_returnStruct(varsd)

代码运行得很好,直到我到达这三行:

varsdb = ct.cast(varsd, returnStruct_t)

print(varsdb.contents.vars[0].x[0][0])

runSolver.free_returnStruct(varsd)

这些行都导致了段错误(seg faults)。如果有人能给我一些建议,让这个代码正常工作,我将非常感激!

错误信息看起来是这样的:

Starting program: /usr/bin/python UserDefinedCode.py
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
 This is the value: 0.000000 
 This is the value: 0.000000 
 This is the value: 0.000000 
 This is the value: 0.000000 
 This is the value: 20.000000 
 This is the value: 203.000000 
 This is the value: 202.000000 
 This is the value: 202.000000 
 This is the value: 40.000000 
 This is the value: 406.000000 
 This is the value: 404.000000 
 This is the value: 404.000000 
 This is the value: 60.000000 
 This is the value: 609.000000 
 This is the value: 606.000000 
 This is the value: 606.000000 

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff33795d4 in ?? () from /usr/lib/python2.7/lib-dynload/_ctypes.so
(gdb) where
#0  0x00007ffff33795d4 in ?? () from /usr/lib/python2.7/lib-dynload/_ctypes.so
#1  0x00007ffff3386ea4 in ffi_call_unix64 () from /usr/lib/python2.7/lib-dynload/_ctypes.so
#2  0x00007ffff33868c5 in ffi_call () from /usr/lib/python2.7/lib-dynload/_ctypes.so
#3  0x00007ffff33772c2 in _ctypes_callproc () from /usr/lib/python2.7/lib-dynload/_ctypes.so
#4  0x00007ffff3377aa2 in ?? () from /usr/lib/python2.7/lib-dynload/_ctypes.so
#5  0x00000000004d91b6 in PyObject_Call ()
#6  0x000000000054c0da in PyEval_EvalFrameEx ()
#7  0x000000000054c272 in PyEval_EvalFrameEx ()
#8  0x0000000000575d92 in PyEval_EvalCodeEx ()
#9  0x00000000004c1352 in PyRun_SimpleFileExFlags ()
#10 0x00000000004c754f in Py_Main ()
#11 0x00007ffff68cb76d in __libc_start_main (main=0x41ba10 <main>, argc=2, ubp_av=0x7fffffffe1d8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe1c8)
    at libc-start.c:226
#12 0x000000000041ba41 in _start ()

1 个回答

2

这里你至少有四个问题(其实有五个,但有一个不重要)。

导致你程序崩溃的那一行通常是:

varsdb = ct.cast(varsd, returnStruct_t)

这是因为你试图把一个 void * 类型转换成 returnStruct_t,而不是 returnStruct_t *。由于 returnStruct_t 的大小远大于一个指针,这样做很可能会超出分配的内存范围。即使没有崩溃,它也会给你一些无效的值。这就相当于下面这段 C 代码:

returnStruct_t varsdb = *(returnStruct_t *)(&varsd);

你想要的其实是:

returnStruct_t *varsdb = (returnStruct_t *)(varsd);

换句话说:

varsdb = ct.cast(varsd, returnStructPointer)

修正这个问题后,我经常还是会在尝试访问 varsdb.contents.vars[0].x[0][0] 时遇到崩溃(不过 varsdb.contents.vars[0].x[0] 本身是安全的)。

下一个问题是你没有正确地定义你的结构体。下面是 C 代码:

typedef struct testStruct {
  double *x[11];
  double *u[10];
} Test;

这是 Python 代码:

class vars_t(ct.Structure):
    _fields_ = [("u", ct.POINTER(ct.c_double*10)),
                    ("x", ct.POINTER(ct.c_double*11))]

你把 ux 搞混了。所以你所称的 x,并把它当作一个包含 11 个 double 的数组,实际上是 u,它是一个包含 10 个 double 的数组。因此每次你访问 x[10] 时,都是在越界。


修正这个问题后,我打印出来的值还是一堆无效值。用 clang 编译时,结果总是接近 6.92987533417e-310

我觉得这个问题完全出在 C 代码里。我经常在 C 代码中的 x[10][4]u[9][2] 行打印出无效的数字。再次使用同样的编译,我得到的合理值和无效值的比例差不多,比如 26815615859885194199148049996411692254958731641184786755447122887443528060147093953603748596333806855380063716372972101707507765623893139892867298012168192.000000,前者有合理值,而后者却是 nan

当我在 valgrind 下运行一个简单的 C 驱动程序时,每四次 fprintf 就会出现一次这个:

==85323== Use of uninitialised value of size 8

所以你可能在 C 代码的分配或初始化中有一个越界错误,有时你能侥幸逃过,但并不总是。


另外,这两者的类型也不一样:

typedef struct returnStruct_t {
  Test* vars;
} ReturnStruct;

class returnStruct_t(ct.Structure):
    _fields_ = [("vars", vars_t*numOpt)]

前者是一个指向 Test 对象数组的单指针,而后者是 3 个 Test 对象。所以,你又一次试图把一个指针当作该类型的值来读取,这样做会超出分配的内存范围。

修正这个问题后,我不再遇到崩溃,最终得到的合理值像 80.0,即使在过程中打印出无效值。但当然,我在过程中还是会看到那些无效的打印,valgrind 也仍在抱怨,所以显然这还不是最后一个问题。


你的代码中还有一个明显的内存泄漏——虽然这与当前问题没有直接关系,但这表明你可能在其他地方也有类似的错误。你是这样分配 x 数组的:

for(x = 0; x < 11; x++)
  varsStruct[i].x[x] = (double *)malloc(sizeof(double)*5);    

… 然后这样释放它们:

for(x = 1; x < 11; x++) 
  free(returnStruct->vars[i].x[x]);

所以 x[0] 从未被释放。

撰写回答