【问题标题】:Calling Cython function from C code raises segmentation fault从 C 代码调用 Cython 函数会引发分段错误
【发布时间】:2019-04-12 08:33:48
【问题描述】:

我正在尝试在 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 函数的打印语句时,不会引发分段错误。但 PI 的值为 0。

【问题讨论】:

    标签: c python-3.x cython


    【解决方案1】:

    我猜你正在使用 Cython 0.29。 Since 0.29, PEP-489 多阶段模块初始化已为 Python 版本 >=3.5 启用。这意味着,正如您所经历的那样,使用 PyInit_XXX 已经不够了。

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

    #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。

    【讨论】:

      【解决方案2】:

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

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

      让我好奇的第一件事是编译这一步花了多长时间:

      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测试(稍作修改:将main中的PyInit_transcendentals改为inittranscendentals),如下图:

      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;
      }
      
      • 你好.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
      

      分段错误(核心转储)

      【讨论】:

      • 谢谢!我也在python2.7上测试过,效果很好。
      • 好像是python 3的bug。
      • OR .. :) 我们都不知道如何为此正确配置 Python3。也许他们在 Python3 中从 Python2 改变了一些东西。但话又说回来,我不知道还有什么要配置的。最好在相关的 python 论坛上发布错误报告。无论如何,更有理由在我的 PC 上保留 Python 2 和 Python 3。 :D
      • 我在 github 上创建了一个关于此的问题。一位投稿人回复了我这个。 “在 Python 3 中调用模块 init 函数(并丢弃它返回的模块引用)不是正确的做法。将其留给 CPython。您可以使用 inittab 机制与静态链接的扩展模块集成。”
      • 哦! :O 酷!我今天学了些新东西。感谢分享@XiaojianLuo。如果可行,那么您可以发布正确的 Python3 代码作为您自己对此问题的答案。
      猜你喜欢
      • 2017-08-18
      • 2013-01-30
      • 2019-11-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多