上市[Python 3.Docs]: ctypes - A foreign function library for Python。
在调查并查看代码后,我得出了一个结论(我从一开始就凭直觉知道发生了什么)。
好像[SciPy.Docs]: numpy.ndarray.ctypes:
_ctypes.data_as(self, obj)
...
返回的指针将保留对数组的引用。
具有误导性。保留引用表示它将保存数组(内部)缓冲区地址(在某种意义上,它不会复制内存内容),而不会 Python 引用 (Py_XINCREF)。
看着[Github]: numpy/numpy - numpy/numpy/core/_internal.py:
def data_as(self, obj):
# Comments
return self._ctypes.cast(self._data, obj)
这是对 ctypes.cast 的调用,它只保存源数组的缓冲区地址。
发生的情况是np.asfortranarray(xmat) 创建了一个临时数组(动态),然后ctypes.data_as 返回其缓冲区地址。在该行之后,临时对象超出范围(其缓冲区也是如此),但仍会引用其地址,从而产生 Undefined Behavior (UB )。
在 v1.15.0([SciPy.Docs]: numpy.ndarray.ctypes(强调是我的))中提到了这一点:
小心使用 ctypes 属性 - 特别是在临时数组或动态构建的数组上。例如,调用(a+b).ctypes.data_as(ctypes.c_void_p)返回一个指向无效内存的指针,因为创建为 (a+b) 的数组在下一条 Python 语句之前被释放。您可以使用c=a+b 或ct=(a+b).ctypes 来避免此问题。在后一种情况下,ct 将持有对数组的引用,直到 ct 被删除或重新分配。
但他们后来把它拿出来了(尽管代码没有被修改(关于这种行为))。
要克服错误,“保存”临时数组或保留 (Python) 引用。 [SO]: Access violation when trying to read out object created in Python passed to std::vector on C++ side and then returned to Python (@CristiFati's answer)也遇到了同样的问题。
我稍微修改了你的代码(包括那些糟糕的名字:))。
code00.py:
#!/usr/bin/env python3
import sys
import ctypes as ct
import numpy as np
from collections import defaultdict
DblPtr = ct.POINTER(ct.c_double)
class Struct0(ct.Structure):
_fields_ = [
("size", ct.c_uint32),
("data", DblPtr),
]
class Wrapper(ct.Structure):
_fields_ = [
("value", Struct0),
]
def test_np(np_array, save_intermediary_array):
wrapper = Wrapper()
wrapper.value.size = np_array.size
if save_intermediary_array:
fortran_array = np.asfortranarray(np_array)
wrapper.value.data = fortran_array.ctypes.data_as(DblPtr)
else:
wrapper.value.data = np.asfortranarray(np_array).ctypes.data_as(DblPtr)
#print(wrapper.value.data[0])
return wrapper.value.data[1]
def main(*argv):
dim1, dim0 = 16, 32
mat = np.ones((dim1, dim0), dtype=np.float64, order="C")
print("NumPy CTypes data: {0:}\n{1:}".format(mat.ctypes, mat.ctypes._ctypes))
dd = defaultdict(int)
flag = 0 # Change to 1 to avoid problem
print("Saving intermediary array: {0:d}".format(flag))
for i in range(100):
dd[test_np(mat, flag)] += 1
print("\nResult: {0:}".format(dd))
if __name__ == "__main__":
print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
print("NumPy version: {0:}".format(np.version.version))
main(*sys.argv[1:])
print("\nDone.")
输出:
e:\Work\Dev\StackOverflow\q059959608>sopr.bat
*** Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ***
[prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe" code01.py
Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 64bit on win32
NumPy version: 1.18.0
NumPy CTypes data: <numpy.core._internal._ctypes object at 0x000001C9744B0348>
<module 'ctypes' from 'c:\\Install\\pc064\\Python\\Python\\03.07.06\\Lib\\ctypes\\__init__.py'>
Saving intermediary array: 0
Result: defaultdict(<class 'int'>, {9.707134377684e-312: 100})
Done.
[prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe" code01.py
Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 64bit on win32
NumPy version: 1.18.0
NumPy CTypes data: <numpy.core._internal._ctypes object at 0x000001842ECA4FC8>
<module 'ctypes' from 'c:\\Install\\pc064\\Python\\Python\\03.07.06\\Lib\\ctypes\\__init__.py'>
Saving intermediary array: 0
Result: defaultdict(<class 'int'>, {1.0: 100})
Done.
[prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe" code01.py
Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 64bit on win32
NumPy version: 1.18.0
NumPy CTypes data: <numpy.core._internal._ctypes object at 0x000001AD586E91C8>
<module 'ctypes' from 'c:\\Install\\pc064\\Python\\Python\\03.07.06\\Lib\\ctypes\\__init__.py'>
Saving intermediary array: 0
Result: defaultdict(<class 'int'>, {9.110668798574e-312: 100})
Done.
[prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe" code01.py
Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 64bit on win32
NumPy version: 1.18.0
NumPy CTypes data: <numpy.core._internal._ctypes object at 0x0000012F903A9188>
<module 'ctypes' from 'c:\\Install\\pc064\\Python\\Python\\03.07.06\\Lib\\ctypes\\__init__.py'>
Saving intermediary array: 0
Result: defaultdict(<class 'int'>, {6.44158096444e-312: 100})
Done.
注意事项:
- 所见结果非常随机,通常是 UB 指标
- 有趣的是,在同一次运行中,它总是相同的值(defaultdict 只有一项)
- 将 flag 更改为 1(或任何评估为 True 的东西)将使问题消失