cython中不同字符串的相同内存地址

时间:2019-05-16 18:08:56

标签: python string memory cython

我用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())

但是打印出来的内存地址都是一样的。我在做什么错了?

1 个答案:

答案 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的地址与包含它的对象的地址不同(正如人们所期望的那样)。