我用cython写了一个树对象,它有许多节点,每个节点都包含一个unicode字符。我想测试如果我使用Py_UNICODE或str作为变量类型,字符是否会被扣留。我试图通过创建节点类的多个实例并为每个实例获取字符的内存地址来进行测试,但是即使不同的实例包含不同的字符,我还是以相同的内存地址结束。这是我的代码:
from libc.stdint cimport uintptr_t
cdef class Node():
cdef:
public str character
public unsigned int count
public Node lo, eq, hi
def __init__(self, str character):
self.character = character
def memory(self):
return <uintptr_t>&self.character[0]
我正在尝试像这样从Python比较内存位置:
a = Node("a")
a2 = Node("a")
b = Node("b")
print(a.memory(), a2.memory(), b.memory())
但是打印出来的内存地址都是一样的。我在做什么错了?
答案 0 :(得分:3)
很明显,您所做的不是您想做的。
self.character[0]
不返回第一个字符的地址/引用(例如,对于数组而言),而是返回Py_UCS4
值(即,无符号的32位整数) ),将其复制到堆栈上的(本地,临时)变量中。
在您的函数中,<uintptr_t>&self.character[0]
为您获取堆栈上的局部变量的地址,每次调用的机会总是相同的,因为调用memory
时始终有相同的堆栈布局。>
为了更清楚一点,这里与char * c_string
的区别在于,&c_string[0]
为您提供c_string
中第一个字符的地址。
比较:
%%cython
from libc.stdint cimport uintptr_t
cdef char *c_string = "name";
def get_addresses_from_chars():
for i in range(4):
print(<uintptr_t>&c_string[i])
cdef str py_string="name";
def get_addresses_from_pystr():
for i in range(4):
print(<uintptr_t>&py_string[i])
现在:
>>> get_addresses_from_chars() # works - different addresses every time
# ...7752
# ...7753
# ...7754
# ...7755
>>> get_addresses_from_pystr() # works differently - the same address.
# ...0672
# ...0672
# ...0672
# ...0672
您可以这样查看:c_string[...]
是cdef
的功能,但是py_string[...]
是python的功能,因此无法按构造返回地址
要影响堆栈布局,可以使用递归函数:
def memory(self, level):
if level==0 :
return <uintptr_t>&self.character[0]
else:
return self.memory(level-1)
现在使用a.memory(0)
,a.memory(1)
等调用它会给您提供不同的地址(除非进行尾部呼叫优化,但我不相信它会发生,但是您可以禁用它可以确定优化(-O0
)。因为根据level
/递归深度,将返回其地址的局部变量在堆栈中的其他位置。
要查看Unicode对象是否被隔离,使用id
就足够了,它会产生对象的地址(这是CPython的实现细节),因此您根本不需要Cython:>
>>> id(a.character) == id(a2.character)
# True
或在Cython中,执行id
一样的操作(快一点):
%%cython
from libc.stdint cimport uintptr_t
from cpython cimport PyObject
...
def memory(self):
# cast from object to PyObject, so the address can be used
return <uintptr_t>(<PyObject*>self.character)
您需要将object
强制转换为PyObject *
,以便Cython允许获取变量的地址。
现在:
>>> ...
>>> print(a.memory(), a2.memory(), b.memory())
# ...5800 ...5800 ...5000
如果要获取unicode对象中第一个代码点的地址(与字符串的地址不同),则可以使用<PY_UNICODE *>self.character
,Cython将通过调用来代替PyUnicode_AsUnicode
,例如:
%%cython
...
def memory(self):
return <uintptr_t>(<Py_UNICODE*>self.character), id(self.character)
现在
>>> ...
>>> print(a.memory(), a2.memory(), b.memory())
# (...768, ...800) (...768, ...800) (...144, ...000)
即"a"
被拘留,并且地址不同于"b"
,并且代码点bufffer的地址与包含它的对象的地址不同(正如人们所期望的那样)。