【问题标题】:Python: memory corruption after successful return from a ctypes foreign functionPython:从 ctypes 外部函数成功返回后内存损坏
【发布时间】:2021-01-12 22:13:45
【问题描述】:

非必要背景:我在使用 ctypes 指针时遇到了许多问题,到目前为止,我可以通过在 Python 端将所有内容剥离到 c_void_p 来解决这些问题,然后挖掘出我需要的东西在C中通过重铸和指针算术。然而,我终于在 Python 中遇到了我无法解决的问题,我希望这个 MWE 的明智答案能让我修复我所有的其他 hack。

MWE:在以下两种情况下,我成功地定义、填充、传递和返回一个结构,从/到 Python 到/从一个简单的 C 外部函数。在第一种情况下(ff1.py),一切都很好。在第二种情况下(ff2.py),函数返回并在 Python 中成功取消引用,然后 Python 崩溃。这两种情况的区别在于,从 Python 传出的结构体是作为变量从模块中导入,还是在传递给 C 之前从 Python 函数返回。

ff.h:

struct dummy
{
  int      dim1;
  int      dim2;
  double * array_ptr;
};

void
foreign_func( struct dummy * ptr_to_dummy );

ff.c:

#include "ff.h"

__attribute__((visibility("default")))
void
foreign_func( struct dummy * d_ptr )
{
  const int N = d_ptr->dim1;
  const int M = d_ptr->dim2;

  for( int i=0; i<N; i++ )
    for( int j=0; j<M; j++ )
      d_ptr->array_ptr[ i*M + j ] *= 3.14159;

  return;
}

struct_wo_funcs.py:

import numpy as np
import ctypes

class dummy_struct_type( ctypes.Structure ):
    _fields_ = [ ( 'dim1',      ctypes.c_int ),
                 ( 'dim2',      ctypes.c_int ),
                 ( 'array_ptr', ctypes.POINTER( ctypes.c_double ) ) ]


n = 12
m = 50
a = np.ones( (n,m), dtype=ctypes.c_double, order='C' )

dummy_struct_instance = \
        dummy_struct_type( ctypes.c_int( n ),
                           ctypes.c_int( m ),
                           a.ctypes.data_as( ctypes.POINTER( ctypes.c_double ) ) )

struct_w_funcs.py:

import numpy as np
import ctypes

class dummy_struct_type( ctypes.Structure ):
    _fields_ = [ ( 'dim1',      ctypes.c_int ),
                 ( 'dim2',      ctypes.c_int ),
                 ( 'array_ptr', ctypes.POINTER( ctypes.c_double ) ) ]


def make_instance( n, m ):
    a = np.ones( (n,m), dtype=ctypes.c_double, order='C' )

    return dummy_struct_type( ctypes.c_int( n ),
                              ctypes.c_int( m ),
                              a.ctypes.data_as( ctypes.POINTER( ctypes.c_double ) ) )

ff1.py:

import numpy as np
import ctypes
import os
from struct_wo_funcs import dummy_struct_type, dummy_struct_instance

fflib = ctypes.CDLL( os.path.abspath( 'ff_lib.so' ) )

fflib.foreign_func.argtypes = [ ctypes.POINTER( dummy_struct_type ) ]
fflib.foreign_func.restype  = None

fflib.foreign_func( ctypes.byref( dummy_struct_instance ) )

print( dummy_struct_instance.array_ptr[19] )

ff2.py:

import numpy as np
import ctypes
import os
from struct_w_funcs import dummy_struct_type, make_instance

fflib = ctypes.CDLL( os.path.abspath( 'ff_lib.so' ) )

fflib.foreign_func.argtypes = [ ctypes.POINTER( dummy_struct_type ) ]
fflib.foreign_func.restype  = None

dummy_struct_instance = make_instance( 12, 50 )

fflib.foreign_func( ctypes.byref( dummy_struct_instance ) )

print( dummy_struct_instance.array_ptr[19] )

我已经在 linux 上使用各种编译器进行了尝试。我还更改了导出并在 Windows 上使用 gcc/MinGW 和 cl/MSVC 编译,效果相同。 (公平地说,在 MSVC 开发人员提示符下运行不会导致彻底崩溃;相反,Windows 会退缩一会儿,然后恢复到提示符而不报告任何内容。)这是一个使用 gcc v7.5.0 和Python 3.6.9:

$ gcc -fPIC -c ff.c && gcc -fPIC -shared -o ff_lib.so ff.o

$ python3 ff1.py 
3.14159

$ python3 ff2.py 
3.14159
free(): corrupted unsorted chunks
Aborted (core dumped)

我已经 valgrinded 搞砸了,只能知道 Python 的内部出了点问题。我对gc 或其他Python 调试工具没有太多经验。所以,即使你不知道答案,我也希望得到一些关于如何进行的建议。最终,我希望能够使用 Python 中的函数来生成我传递出去的结构;也就是说,我希望ff2.py 不会崩溃。如果您的答案只是另一种解决方法(例如 numpy 声明和 byrefs 的某种神奇组合等),那对我来说已经足够了。据我所知,这是 Python 而不是 C 的问题,但如果您可以通过更改 FFI 的 C 端使其工作,我很想看看如何。

【问题讨论】:

    标签: python c numpy ctypes


    【解决方案1】:

    @antti-haapala 果然!这是我的工作版本:

    struct_w_wrapper.py:

    import numpy as np
    import ctypes
    
    class dummy_struct_type( ctypes.Structure ):
        _fields_ = [ ( 'dim1',      ctypes.c_int ),
                     ( 'dim2',      ctypes.c_int ),
                     ( 'array_ptr', ctypes.POINTER( ctypes.c_double ) ) ]
    
    ##
    ##  the wrapper
    ##   the arguments are obviously tailored to
    ##   dummy_struct_type, not to an arbitrary
    ##   "struct_to_be_wrapped"
    ##
    def dont_free_me( struct_to_be_wrapped ):
        class wrapper:
            def __init__( self, dim1, dim2, arr ):
                self.dim1   = dim1  ##  these two ints should persist
                self.dim2   = dim2  ##  without wrapping, but whatever
                self.arr    = arr
                self.struct = \
                        struct_to_be_wrapped( ctypes.c_int( dim1 ),
                                              ctypes.c_int( dim2 ),
                                              arr.ctypes.data_as(
                                                  ctypes.POINTER(
                                                      ctypes.c_double )))
            def the_struct( self ):
                return self.struct
    
        return wrapper
                                                    
    
    wrapped_dummy_struct_type = dont_free_me( dummy_struct_type )
    
    def make_instance( n, m ):
        a = np.ones( (n,m), dtype=ctypes.c_double, order='C' )
    
        return wrapped_dummy_struct_type( n, m, a )
    

    ff3.py:

    import numpy as np
    import ctypes
    import os
    from struct_w_wrapper import dummy_struct_type, make_instance
    
    fflib = ctypes.CDLL( os.path.abspath( 'ff_lib.so' ) )
    
    fflib.foreign_func.argtypes = [ ctypes.POINTER( dummy_struct_type ) ]
    fflib.foreign_func.restype  = None
    
    wrapped_dummy_struct_instance = make_instance( 12, 50 )
    dummy_struct_instance = wrapped_dummy_struct_instance.the_struct()
    
    fflib.foreign_func( ctypes.byref( dummy_struct_instance ) )
    
    print( dummy_struct_instance.array_ptr[19] )
    

    然后,根据提示:

    $ python3 ff3.py 
    3.14159
    

    更新:我现在知道这是 Python ctypes 中一个古老的已知功能 (#12836),numpy's ctypes interface 有一个内置的解决方法。有一个 data 方法反映了这个 SO question 中有问题的 data_as 行为;还有 numpy 自己的 data_as 方法,这里有我们想要的:“返回的指针将保留对数组的引用。”

    由于我的问题导致了崩溃,因此我提出了一个 Python 错误 (#41883),如果您有兴趣实施自己的解决方法,可以进行一些很好的讨论。 This other SO answer 也是不错的阅读方式。

    【讨论】:

      【解决方案2】:

      指向数组数据的 ctypes 指针不会使数组保持活动状态。您可以通过 del a 使第一个崩溃。

      为结构创建一个包装器,该包装器还通过持有对它的引用来保持 numpy 数组的活动。

      【讨论】:

        猜你喜欢
        • 2021-11-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-13
        • 1970-01-01
        • 2018-06-23
        • 2012-11-20
        相关资源
        最近更新 更多