【问题标题】:how to memset a unicode string in python 2.7如何在python 2.7中memset一个unicode字符串
【发布时间】:2018-03-25 06:49:36
【问题描述】:

我有一个 unicode 字符串 f。我想将它设置为 0。 print f 应该显示 null (\0)

我正在使用 ctypes.memset 来实现这一点 -

>     >>> f
>     u'abc'
>     >>> print ("%s" % type(f))
>     <type 'unicode'>
>     >>> import ctypes
>     **>>> ctypes.memset(id(f)+50,0,6)**
>     **4363962530
>     >>> f
>     u'abc'
>     >>> print f
>     abc**

为什么在unicode字符串的情况下内存位置没有得到memset? 它非常适合 str 对象。

感谢您的帮助。

【问题讨论】:

  • PyUnicode_ObjectPyString_Object 的内部格式不同,因此没有理由期望相同的偏移量会找到两者的缓冲区。
  • 另外,这应该是不言而喻的,但是……这对于任何目的来说都是一个非常糟糕的主意,除了探索 CPython 的实现。解释器期望字符串是不可变的,如果你违反它,各种事情都会破坏。
  • 你从哪里知道这意味着缓冲区的偏移量是 50?
  • 不,字符串类型的偏移量是 36,而不是 37。(这仅在 64 位 Python 上有效,并且仅在大多数而非所有 64 位平台上有效)。这不是您可以应用于任何类型的神奇规则;您必须通过查看 C 结构的定义方式并了解平台的 C 布局规则来弄清楚这一点。
  • 我已经写了一个答案来解释如何做到这一点 - 但真的,真的,真的不要这样做。

标签: python python-2.7 unicode python-unicode unicode-string


【解决方案1】:

首先,这几乎可以肯定是一个非常糟糕的主意。 Python 期望字符串是不可变的。有一个原因是即使 C API 也不允许您在它们被标记为就绪后更改它们的内容。如果您只是为了玩转解释器的实现而这样做,那可能会很有趣且很有启发性,但如果您是出于任何现实目的而这样做,那么您可能做错了什么。

特别是,如果您是为了“安全”而这样做,那么您几乎可以肯定真正想做的是首先不要创建unicode,而是创建一个bytearray,例如字符串的 UTF-16 或 UTF-32 编码,可以以一种安全、便携且更容易的方式将其归零。


无论如何,没有理由期望两种完全不同的类型应该将它们的缓冲区存储在相同的偏移量处。


在 CPython 2.x 中,strPyStringObject

typedef struct {
    PyObject_VAR_HEAD
    long ob_shash;
    int ob_sstate;
    char ob_sval[1];
} PyStringObject;

ob_sval 是缓冲区;偏移量在 64 位版本上应该是 36,(我认为)在 32 位版本上应该是 24。

在评论中,你说:

我在某处读到它,并且字符串类型的偏移量在我的系统中是 37,这就是 sys.getsizeof('') 显示的 -> >>> sys.getsizeof('') 37

字符串缓冲区的偏移量实际上是 36,而不是 37。而且它甚至接近这一事实只是 str 实现方式的巧合。 (希望您可以通过查看struct 的定义来理解为什么——如果不是,您绝对不应该编写这样的代码。)没有理由期望同样的技巧适用于其他人键入而不查看其实现。


unicodePyUnicodeObject

typedef struct {
    PyObject_HEAD
    Py_ssize_t length;          /* Length of raw Unicode data in buffer */
    Py_UNICODE *str;            /* Raw Unicode buffer */
    long hash;                  /* Hash value; -1 if not set */
    PyObject *defenc;           /* (Default) Encoded version as Python
                                   string, or NULL; this is used for
                                   implementing the buffer protocol */
} PyUnicodeObject;

它的缓冲区甚至不在对象内部; str 成员是指向缓冲区的指针(不保证在结构之后)。它的偏移量在 64 位版本上应该是 24,而(我认为)在 32 位版本上应该是 20。因此,要执行等效操作,您需要读取那里的指针,然后按照它找到 memset 的位置。

如果您使用的是窄 Unicode 构建,它应该如下所示:

>>> ctypes.POINTER(ctypes.c_uint16 * len(g)).from_address(id(g)+24).contents[:]
[97, 98, 99]

这是查找(uint16_t *)(((char *)g)+24) 并读取从*that 开始并以*(that+len(g)) 结束的数组的ctypes 转换,如果您正在编写C 代码并且没有访问权限,那么您必须这样做到unicodeobject.h 标头。

(在我刚刚引用的测试中,g 位于 0x10a598090,而它的src 指向0x10a3b09e0,因此缓冲区不是紧跟在结构之后,也不是在它附近的任何地方;它在它之前大约 2MB .)

对于宽 Unicode 构建,与 c_uint32 相同。

所以,这应该告诉你你想memset

您还应该看到您在此处尝试“安全”的严重影响。 (如果我必须指出这一点,这又表明您不应该编写此代码。)

【讨论】:

  • @DS' 打印出指针ctypes.c_char_p.from_addrss(id(g)+24) 中的值。 (这显然不是正确的指针类型,但由于我们不打算取消引用它或对它进行指针算术运算,只需读取它指向的地址,我使用最简单的类型。)
  • @DS' 虽然真的,我会创建一个ctypes.Structure 来表示PyUnicodeObject,然后看看PyUnicodeObject.from_address(id(g)).str,它已经是POINTER(c_uint16)I did the equivalent for Python 3.4' 说服自己我理解新的、更复杂的内部字符串表示是如何工作的。
猜你喜欢
  • 1970-01-01
  • 2014-01-13
  • 2012-03-23
  • 2018-03-07
  • 1970-01-01
  • 2014-04-23
  • 1970-01-01
  • 1970-01-01
  • 2013-10-04
相关资源
最近更新 更多