【问题标题】:Segfault when import_array not in same translation unitimport_array 不在同一个翻译单元中时的段错误
【发布时间】:2015-11-05 09:57:31
【问题描述】:

我在正确初始化 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

file1.cpp

#include "header1.hpp"

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

void initialize()
{
  wrap_import_array();
}

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

编译器命令:

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

我已经使用 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 中运行它。

最让我困惑的是this C++ NumPy wrapper 似乎在不同的翻译单元中调用import_array。

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

【问题讨论】:

    标签: python c++ python-2.7 python-3.x numpy


    【解决方案1】:

    在挖掘 NumPy 标头后,我想我找到了解决方案:

    在numpy/__multiarray_api.h 中有一节处理内部API 缓冲区的位置。为简洁起见,这里是相关的 sn-p:

    #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
    

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

    让多个翻译单元使用相同的内部 API 缓冲区的一致方法是在每个模块中,将 PY_ARRAY_UNIQUE_SYMBOL 定义为某个库唯一名称,然后每个翻译单元除了定义 import_array 包装器的翻译单元 定义NO_IMPORT 或NO_IMPORT_ARRAY。顺便说一句,ufunc 功能也有类似的宏:PY_UFUNC_UNIQUE_SYMBOL 和 NO_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
    

    file1.cpp

    #define MYLIBRARY_USE_IMPORT
    #include "header1.hpp"
    
    void* wrap_import_array()
    {
      import_array();
      return (void*) 1;
    }
    
    void initialize()
    {
      wrap_import_array();
    }
    

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

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

    【讨论】:

    • 你能分享一下MYLIBRARY_ARRAY_API的定义吗?谢谢。
    • MYLIBRARY_ARRAY_API 只是用于存储 NumPy 内部缓冲区的示例名称。它不是宏,但它应该是您的应用程序/库所独有的,以防止与其他库发生冲突。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-06-15
    • 1970-01-01
    • 2014-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多