6

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.

2 Answers2

7

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 while compiling.

ead
  • 32,758
  • 6
  • 90
  • 153
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)

Duck Dodgers
  • 3,409
  • 8
  • 29
  • 43
  • Thank you! I also tested on python2.7, and it works. – Xiaojian Luo Apr 12 '19 at 11:34
  • It seems to be a bug of python 3. – Xiaojian Luo Apr 12 '19 at 11:34
  • OR .. `:)` we both don't know how to correctly configure Python3 for this. Maybe they changed something in Python3 from Python2. But then again, I am not aware of what else to configure. Better put up a bug report on the relevant python forum. Anyways, all the more reason to keep Python 2 as well as Python 3 on my PC. `:D` – Duck Dodgers Apr 12 '19 at 11:38
  • I created an issue about this on github. An contributor replied me this. "Calling the module init function (and throwing away the module reference that it returns) is not the right thing to do in Python 3. Leave that to CPython. You can use the inittab mechanism to integrate with a statically linked extension module." – Xiaojian Luo Apr 12 '19 at 12:31
  • oh! `:O` cool! I learned something new today. Thanks for sharing that @XiaojianLuo. If that works, then you can post that correct Python3 code as your own answer to this question. – Duck Dodgers Apr 12 '19 at 13:51
  • yes cool ! I am always happy to learn something. I upvoted the correct answer. `:)` – Duck Dodgers Apr 15 '19 at 08:03