Mak*_*s_F 3 c++ python arrays ctypes python-3.4
编辑 3
我有一些从 python 访问的 C++ 代码(externed 为 C)。我想double**在 python 中分配一个,将它传递给 C/C++ 代码以复制类内部数据的内容,然后在 python 中使用它,类似于我将如何使用列表列表。
不幸的是,我无法为 python 指定最内部数组的大小,因此它在迭代它和程序段错误时读取无效内存。
我无法在 C++ 中更改内部数据的结构,我想让 python 为我做边界检查(就像我使用的是 c_double_Array_N_Array_M 而不是指针数组)。
test.cpp(用 编译g++ -Wall -fPIC --shared -o test.so test.cpp)
#include <stdlib.h>
#include <string.h>
class Dummy
{
double** ptr;
int e;
int i;
};
extern "C" {
void * get_dummy(int N, int M) {
Dummy * d = new Dummy();
d->ptr = new double*[N];
d->e = N;
d->i = M;
for(int i=0; i<N; ++i)
{
d->ptr[i]=new double[M];
for(int j=0; j <M; ++j)
{
d->ptr[i][j] = i*N + j;
}
}
return d;
}
void copy(void * inst, double ** dest) {
Dummy * d = static_cast<Dummy*>(inst);
for(int i=0; i < d->e; ++i)
{
memcpy(dest[i], d->ptr[i], sizeof(double) * d->i);
}
}
void cleanup(void * inst) {
if (inst != NULL) {
Dummy * d = static_cast<Dummy*>(inst);
for(int i=0; i < d->e; ++i)
{
delete[] d->ptr[i];
}
delete[] d->ptr;
delete d;
}
}
}
Run Code Online (Sandbox Code Playgroud)
Python(这个段错误。把它放在 test.so 所在的同一个目录中)
import os
from contextlib import contextmanager
import ctypes as ct
DOUBLE_P = ct.POINTER(ct.c_double)
library_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test.so')
lib = ct.cdll.LoadLibrary(library_path)
lib.get_dummy.restype = ct.c_void_p
N=15
M=10
@contextmanager
def work_with_dummy(N, M):
dummy = None
try:
dummy = lib.get_dummy(N, M)
yield dummy
finally:
lib.cleanup(dummy)
with work_with_dummy(N,M) as dummy:
internal = (ct.c_double * M)
# Dest is allocated in python, it will live out of the with context and will be deallocated by python
dest = (DOUBLE_P * N)()
for i in range(N):
dest[i] = internal()
lib.copy(dummy, dest)
#dummy is not available anymore here. All the C resources has been cleaned up
for i in dest:
for n in i:
print(n) #it segfaults reading more than the length of the array
Run Code Online (Sandbox Code Playgroud)
我可以在我的 python 代码中更改什么,以便我可以将数组视为列表?(我只需要阅读它)
以便 Python 在迭代时知道数组的大小
数据
此解决方案适用于二维数组或指向数组的指针数组,只需稍加修改,无需使用 numpy 之类的库。
我将使用 int 作为类型而不是 double 并且我们将复制源,其定义为
N = 10;
M = 15;
int ** source = (int **) malloc(sizeof(int*) * N);
for(int i=0; i<N; ++i)
{
source[i] = (int *) malloc(sizeof(int) * M);
for(int j=0; j<M; ++j)
{
source[i][j] = i*N + j;
}
}
Run Code Online (Sandbox Code Playgroud)
Python 分配
dest = ((ctypes.c_int * M) * N) ()
int_P = ctypes.POINTER(ctypes.c_int)
temp = (int_P * N) ()
for i in range(N):
temp[i] = dest[i]
lib.copy(temp)
del temp
# temp gets collected by GC, but the data was stored into the memory allocated by dest
# You can now access dest as if it was a list of lists
for row in dest:
for item in row:
print(item)
Run Code Online (Sandbox Code Playgroud)
C复制功能
void copy(int** dest)
{
for(int i=0; i<N; ++i)
{
memcpy(dest[i], source[i], sizeof(int) * M);
}
}
Run Code Online (Sandbox Code Playgroud)
解释
我们首先分配一个二维数组。2Darray[N][M]被分配为 1D array[N*M],具有2d_array[n][m] == 1d_array[n*M + m]. 由于我们的代码需要 a int**,但我们的二维数组分配为 a int *,因此我们创建了一个临时数组来提供预期的结构。
我们分配temp[N][M],然后分配我们之前分配的内存的地址temp[n] = 2d_array[n] = &1d_array[n*M](第二个等号用于显示我们分配的实际内存发生了什么)。
如果您更改复制代码,使其复制的数量超过 M,比如说 M+1,您将看到它不会出现段错误,但它会覆盖下一行的内存,因为它们是连续的(如果您更改复制代码,记得把python中分配的dest的大小加1,否则在最后一行的最后一项之后写会segfault)
Python 分配
int_P = ctypes.POINTER(ctypes.c_int)
inner_array = (ctypes.c_int * M)
dest = (int_P * N) ()
for i in range(N):
dest[i] = inner_array()
lib.copy(dest)
for row in dest:
# Python knows the length of dest, so everything works fine here
for item in row:
# Python doesn't know that row is an array, so it will continue to read memory without ever stopping (actually, a segfault will stop it)
print(item)
dest = [internal[:M] for internal in dest]
for row in dest:
for item in row:
# No more segfaulting, as now python know that internal is M item long
print(item)
Run Code Online (Sandbox Code Playgroud)
C复制功能
Same as for solution 1
Run Code Online (Sandbox Code Playgroud)
解释
这次我们分配了一个实际的数组指针数组,就像分配了源一样。
由于最外面的数组 ( dest ) 是一个指针数组,python 不知道指向的数组的长度(它甚至不知道这是一个数组,它也可能是一个指向单个 int 的指针)。
如果您遍历该指针,python 将不会绑定检查,它将开始读取您的所有内存,从而导致段错误。
因此,我们将指针切片取前 M 个元素(实际上是数组中的所有元素)。现在 python 知道它应该只迭代前 M 个元素,它不会再出现段错误。
我相信 python 使用此方法复制指向新列表的内容(请参阅来源)
Eryksun 在评论中加入并提出了一个解决方案,以避免复制新列表中的所有元素。
Python 分配
int_P = ctypes.POINTER(ctypes.c_int)
inner_array = (ctypes.c_int * M)
inner_array_P = ctypes.POINTER(inner_array)
dest = (int_P * N) ()
for i in range(N):
dest[i] = inner_array()
lib.copy(dest)
dest_arrays = [inner_array_p.from_buffer(x)[0] for x in dest]
for row in dest_arrays:
for item in row:
print(item)
Run Code Online (Sandbox Code Playgroud)
C复制代码
Same as for solution 1
Run Code Online (Sandbox Code Playgroud)
只有当您可以在 C 端更改复制代码时,此方法才是一个选项。source将不需要更改。
Python 分配
dest = ((ctypes.c_int * M) * N) ()
lib.copy(dest)
for row in dest:
for item in row:
print(item)
Run Code Online (Sandbox Code Playgroud)
C复制功能
void copy(int * dest) {
for(int i=0; i < N; ++i)
{
memcpy(&dest[i * M], source[i], sizeof(int) * M);
}
}
Run Code Online (Sandbox Code Playgroud)
解释
这一次,就像1)我们分配一个连续的二维数组一样。但是由于我们可以更改 C 代码,我们不需要创建不同的数组并复制指针,因为我们将为 C 提供预期的类型。
在复制函数中,我们传递每一行第一项的地址,然后我们复制该行中的 M 个元素,然后我们转到下一行。
复制模式与 case 完全一样1),但这次不是在 python 中编写接口,以便 C 代码以它期望的方式接收数据,我们更改了 C 代码以期望精确格式的数据。
如果您保留此 C 代码,您也可以使用 numpy 数组,因为它们是二维行主要数组。
感谢@eryksun 在原始问题下方的精彩(简洁)评论,所有这些答案都是可能的。