Calling Cython function from C code raises segment

2020-02-12 08:14发布

问题:

I'm trying to call cython (cdef) function in C program. When the cdef function contains python statements, e.g. print(0.5), or python (def) functions, calling the (cdef) function raises a segmentation fault.

The .pyx file:

# cython: language_level=3

cdef public double PI = 3.1415926

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

The .c file:

#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;
}

The compiling commands:

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

When I remove the print statement of get_e function, no segmentation fault would be raised. But the value of PI will be 0.

回答1:

I guess you are using Cython 0.29. Since 0.29, PEP-489 multi-phase module initialisation has been enabled for Python versions >=3.5. This means, using PyInit_XXX is no longer sufficient, as you are experiencing.

Cython's documentation suggest to use inittab mechanism, i.e. your main-function should look something like:

#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;
}

Another possibility to restore the old behavior would be to define macro CYTHON_PEP489_MULTI_PHASE_INIT=0 and thus overriding the default by e.g. passing -DCYTHON_PEP489_MULTI_PHASE_INIT=0 to gcc on the command line.



回答2:

This seems like a bug (or at least an issue with Python3.7).

I tested your example on my Arch Linux with Python3.7.

First thing which made me curious was how long the compilation took on this step:

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

I have a not so bad computer but it took it a couple of minutes to get this compilation done. Strange.

And upon running ./a.out, I also got a segmentation error, like you.


So, then I decided to test (with one minor modification: change PyInit_transcendentals to inittranscendentals in main) with Python2.7, as shown below:

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

The compilation was instant.

I ran ./a.out and the output was:

called get_e():2.718282calling get_e()
pi**e: 22.459157


Then just to be sure, that this had nothing to do with any flags that you might be using, nor that the math library nor something else would be having an effect here, I repeated the test with a very simple "hello world" example as shown below.

  • 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!"

Then,

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

The output was,

hello!

On the other hand, recompling with Python3.7 (after changing inithello to PyInit_hello) gave the following output:

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

Segmentation fault (core dumped)