Листинг [Python 3.Docs]: ctypes - библиотека сторонних функций для 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 возвращает адрес буфера. После строки временный объект выходит из области действия (как и его буфер), но на его адрес все еще ссылаются, что приводит к неопределенному поведению ( UB ).
В v1.15.0 ( [SciPy.Docs]: numpy .ndarray.ctypes ( выделение is мой)) это упоминается:
Будьте осторожны, используя атрибут ctypes - особенно для временных массивов или массивов, созданных на лету. Например, вызов (a+b).ctypes.data_as(ctypes.c_void_p)
возвращает указатель на память, которая недопустима, поскольку массив, созданный как (a + b), освобождается перед следующим Python оператором . Вы можете избежать этой проблемы, используя c=a+b
или ct=(a+b).ctypes
. В последнем случае ct будет хранить ссылку на массив до тех пор, пока ct не будет удален или переназначен.
, но впоследствии они его удалили (хотя код не был изменен (относительно этого поведение)).
Чтобы обойти ошибку, «сохраните» временный массив или сохраните (Python) ссылку на него. Та же проблема возникла в [SO]: нарушение прав доступа при попытке считывания объекта, созданного в Python, переданного в std :: vector на стороне C ++ и затем возвращенного в Python (ответ @ CristiFati) .
Я немного изменил ваш код (включая эти ужасные имена :)).
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 to 1 (или что-либо, что оценивается как True ) заставит проблему исчезнуть