从C代码调用Cython函数会引发分段错误

时间:2019-04-12 08:33:48

标签: c python-3.x cython

我正在尝试在C程序中调用cython(cdef)函数。当cdef函数包含python语句时,例如print(0.5)或python(def)函数,调用(cdef)函数会引发分段错误。

.pyx文件:

# cython: language_level=3

cdef public double PI = 3.1415926

cdef public double get_e():
    print("calling get_e()")
    return 2.718281828

.c文件:

#include "Python.h"
#include "transcendentals.h"
#include <math.h>
#include <stdio.h>

int main(int argc, char **argv) {
  Py_Initialize();
  PyInit_transcendentals();
  printf("pi**e: %f\n", pow(PI, get_e()));
  Py_Finalize();
  return 0;
}

编译命令:

cython transcendentals.pyx

gcc -I. -I/usr/include/python3.5m -I/usr/include/python3.5m \
-Wno-unused-result -Wsign-compare \
-g -fstack-protector-strong -Wformat \
-Werror=format-security -DNDEBUG -g \
-fwrapv -O3 -Wall -Wstrict-prototypes \
-L/usr/lib/python3.5/config-3.5m-x86_64-linux-gnu \
-L/usr/lib transcendentals.c main.c \
-lpython3.5m -lpthread -ldl -lutil -lm -Xlinker \
-export-dynamic -Wl,-O1 -Wl,-Bsymbolic-functions

当我删除get_e函数的print语句时,不会出现任何分段错误。但是PI的值为0。

2 个答案:

答案 0 :(得分:2)

这似乎是一个错误(或者至少是 Python3.7 的问题)。

我在使用Python3.7的Arch Linux上测试了您的示例。

让我感到好奇的第一件事是编译完成了多长时间:

gcc -I. -I/usr/include/python3.7m -I/usr/include/python3.7m -Wno-unused-result \
-Wsign-compare -g -fstack-protector-strong -Wformat -Werror=format-security -g \
-fwrapv -O0 -Wall -Wstrict-prototypes -L/usr/lib/python3.7/config-3.7m-x86_64-linux-gnu \
-L/usr/lib transcendentals.c main.c -lpython3.7m -lpthread -ldl -lutil -lm

我的计算机还不错,但是花了几分钟才完成编译。奇怪。

运行./a.out后,像您一样,我也遇到了细分错误。


因此,然后我决定使用Python2.7测试(做一个小的修改:将PyInit_transcendentals中的inittranscendentals更改为main),如下所示:

gcc -I. -I/usr/include/python2.7 -I/usr/include/python2.7 -Wno-unused-result \
-Wsign-compare -g -fstack-protector-strong -Wformat -Werror=format-security \
-g -fwrapv -O0 -Wall -Wstrict-prototypes -L/usr/lib/python2.7/config-2.7-x86_64-linux-gnu \
-L/usr/lib transcendentals.c main.c -lpython2.7 -lpthread -ldl -lutil -lm

编译是即时的。

我运行了./a.out,输出为:

  

调用了get_e():2.718282调用了get_e()
  pi ** e:22.459157


然后只是要确定,这与您可能使用的任何标志都没有关系,数学库或其他任何东西都不会在这里产生影响,我用一个非常简单的“ hello world”重复了该测试如下所示。

  • main.c
#include <Python.h>
#include "hello.h"

int main() {
  Py_Initialize();
  inithello();
  hello();
  Py_Finalize();
  return 0;
}
  • hello.c
# cython: language_level=2

cdef public hello():
    print "hello!"

然后

cython hello.pyx
cc -c *.c -I /usr/include/python2.7/
cc -L /usr/lib/python2.7/ -lpython2.7 -ldl *.o -o main
./main

输出为

  

你好!

另一方面,使用Python3.7重新编译(将inithello更改为PyInit_hello之后)给出了以下输出:

cc -c *.c -I /usr/include/python3.7m/
cc -L /usr/lib/python3.7/ -lpython3.7m -ldl *.o -o main
./main
  

分段错误(核心已转储)

答案 1 :(得分:2)

我猜您正在使用Cython 0.29。已为大于等于3.5的Python版本启用Since 0.29PEP-489多阶段模块初始化。这意味着,根据您的经验,使用PyInit_XXX不再足够。

Cython's documentation建议使用inittab mechanism,即您的main功能应类似于:

#include "Python.h"
#include "transcendentals.h"
#include <math.h>
#include <stdio.h>

int main(int argc, char **argv) {
  int status=PyImport_AppendInittab("transcendentals", PyInit_transcendentals);
  if(status==-1){
    return -1;//error
  } 
  Py_Initialize();
  PyObject *module = PyImport_ImportModule("transcendentals");

  if(module==NULL){
     Py_Finalize();
     return -1;//error
  }

  printf("pi**e: %f\n", pow(PI, get_e()));
  Py_Finalize();
  return 0;
}

恢复旧行为的另一种可能性是定义宏CYTHON_PEP489_MULTI_PHASE_INIT=0并因此将默认值覆盖例如。在命令行上将-DCYTHON_PEP489_MULTI_PHASE_INIT=0传递给gcc。