当import_array不在同一翻译单元中时的Segfault

hel*_*922 7 c++ python numpy python-2.7 python-3.x

我在使NumPy C API正确初始化时遇到问题.我想我已经把问题import_array从一个不同的翻译单元中调出来了,但我不知道为什么这个问题很重要.

最小的工作示例:

header1.hpp

#ifndef HEADER1_HPP
#define HEADER1_HPP
#include <Python.h>
#include <numpy/npy_3kcompat.h>
#include <numpy/arrayobject.h>

void initialize();

#endif
Run Code Online (Sandbox Code Playgroud)

file1.cpp

#include "header1.hpp"

void* wrap_import_array()
{
  import_array();
  return (void*) 1;
}

void initialize()
{
  wrap_import_array();
}
Run Code Online (Sandbox Code Playgroud)

file2.cpp

#include "header1.hpp"

#include <iostream>

void* loc_wrap_import_array()
{
  import_array();
  return (void*) 1;
}

void loc_initialize()
{
  loc_wrap_import_array();
}

int main()
{
  Py_Initialize();
#ifdef USE_LOC_INIT
  loc_initialize();
#else
  initialize();
#endif
  npy_intp dims[] = {5};
  std::cout << "creating descr" << std::endl;
  PyArray_Descr* dtype = PyArray_DescrFromType(NPY_FLOAT64);
  std::cout << "zeros" << std::endl;
  PyArray_Zeros(1, dims, dtype, 0);
  std::cout << "cleanup" << std::endl;
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

编译器命令:

g++ file1.cpp file2.cpp -o segissue -lpython3.4m -I/usr/include/python3.4m -DUSE_LOC_INIT
./segissue
# runs fine

g++ file1.cpp file2.cpp -o segissue -lpython3.4m -I/usr/include/python3.4m
./segissue
# segfaults
Run Code Online (Sandbox Code Playgroud)

我已经使用Clang 3.6.0,GCC 4.9.2,Python 2.7和Python 3.4(经过适当修改,wrap_import_array因为这在Python 2.x和3.x之间有所不同)进行了测试.各种组合都会产生相同的结果:如果我不打电话loc_initialize,程序将在PyArray_DescrFromType通话中出现段错误.我有NumPy版本1.8.2.作为参考,我在Ubuntu 15.04中运行它.

最令我困惑的是这个C++ NumPy包装器似乎可以通过import_array不同的翻译单元来调用它.

我错过了什么?为什么我必须import_array从同一个翻译单元打电话才能真正生效?更重要的是,当我import_array从Boost.NumPy包装器这样的其他翻译单元调用时,如何让它工作呢?

hel*_*922 5

在深入了解NumPy标题后,我想我找到了一个解决方案:

numpy/__multiarray_api.h,有一节处理内部API缓冲区应该在哪里.为简明扼要,这是相关的片段:

#if defined(PY_ARRAY_UNIQUE_SYMBOL)
#define PyArray_API PY_ARRAY_UNIQUE_SYMBOL
#endif

#if defined(NO_IMPORT) || defined(NO_IMPORT_ARRAY)
extern void **PyArray_API;
#else
#if defined(PY_ARRAY_UNIQUE_SYMBOL)
void **PyArray_API;
#else
static void **PyArray_API=NULL;
#endif
#endif
Run Code Online (Sandbox Code Playgroud)

看起来这是为了让多个模块定义自己的内部API缓冲区,其中每个模块都必须调用自己的import_array定义.

让几个翻译单元使用相同的内部API缓冲区的一致方法是在每个模块中,定义PY_ARRAY_UNIQUE_SYMBOL一些库的唯一名称,然后除了定义import_array包装器的那个之外的每个翻译单元定义NO_IMPORTNO_IMPORT_ARRAY.顺便提一下,ufunc功能有类似的宏:PY_UFUNC_UNIQUE_SYMBOLNO_IMPORT/ NO_IMPORT_UFUNC.

修改后的工作示例:

header1.hpp

#ifndef HEADER1_HPP
#define HEADER1_HPP

#ifndef MYLIBRARY_USE_IMPORT
#define NO_IMPORT
#endif

#define PY_ARRAY_UNIQUE_SYMBOL MYLIBRARY_ARRAY_API
#define PY_UFUNC_UNIQUE_SYMBOL MYLIBRARY_UFUNC_API

#include <Python.h>
#include <numpy/npy_3kcompat.h>
#include <numpy/arrayobject.h>

void initialize();

#endif
Run Code Online (Sandbox Code Playgroud)

file1.cpp

#define MYLIBRARY_USE_IMPORT
#include "header1.hpp"

void* wrap_import_array()
{
  import_array();
  return (void*) 1;
}

void initialize()
{
  wrap_import_array();
}
Run Code Online (Sandbox Code Playgroud)

file2.cpp

#include "header1.hpp"

#include <iostream>

int main()
{
  Py_Initialize();
  initialize();
  npy_intp dims[] = {5};
  std::cout << "creating descr" << std::endl;
  PyArray_Descr* dtype = PyArray_DescrFromType(NPY_FLOAT64);
  std::cout << "zeros" << std::endl;
  PyArray_Zeros(1, dims, dtype, 0);
  std::cout << "cleanup" << std::endl;
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

我不知道这个hack有什么陷阱,或者是否有更好的选择,但这似乎至少在没有任何段错误的情况下编译和运行.