小能豆

Python 结构分配什么值,它始终停留在 0 处?

py

我正在编写一个模块来压缩要传递给 C 程序的位,但一直出错。经过一些测试,我发现 Blah 类的字段 a 无论如何都停留在 0。有人知道这是错误还是我在这里做错了什么吗?

抱歉,我忘了说我使用的是来自http://www.python.org/download/releases/3.1.2/的 python 3.1.2

>>> import ctypes
>>> class Blah(ctypes.Structure):
...     _fields_ = [("a", ctypes.c_uint64, 64),
...                 ("b", ctypes.c_uint16, 16),
...                 ("c", ctypes.c_uint8, 8),
...                 ("d", ctypes.c_uint8, 8)]
...
>>> x = Blah(0xDEAD,0xBEEF,0x44,0x12)
>>> print (hex(x.a) )
0x0
>>> print (hex(x.b ))
0xbeef
>>> print (hex(x.c ))
0x44
>>> print (hex(x.d ))
0x12
>>>
>>> g = Blah(0x2BAD,0xBEEF,0x55,0x12)
>>> print (hex(g.a ))
0x0
>>> print (hex(g.b ))
0xbeef
>>> print (hex(g.c ))
0x55
>>> print (hex(g.d ))
0x12
>>>

交换前两个字段的位置得到相同的结果

>>> import ctypes
>>> class Blah(ctypes.Structure):
...     _fields_ = [("a", ctypes.c_uint16, 16),
...                 ("b", ctypes.c_uint64, 64),
...                 ("c", ctypes.c_uint8, 8),
...                 ("d", ctypes.c_uint8, 8)]
...
>>> x = Blah(0xDEAD,0xBEEF,0x44,0x12)
>>> print (hex(x.a) )
0xdead
>>> print (hex(x.b ))
0x0
>>> print (hex(x.c ))
0x44
>>> print (hex(x.d ))
0x12
>>>
>>> g = Blah(0x2BAD,0xBEEF,0x55,0x12)
>>> print (hex(g.a ))
0x2bad
>>> print (hex(g.b ))
0x0
>>> print (hex(g.c ))
0x55
>>> print (hex(g.d ))
0x12
>>>

改变字段的大小,我观察到输入的一些奇怪的截止

>>> import ctypes
>>> class Blah(ctypes.Structure):
...     _fields_ = [("a", ctypes.c_uint64, 40),
...                 ("b", ctypes.c_uint64, 40),
...                 ("c", ctypes.c_uint8, 8),
...                 ("d", ctypes.c_uint8, 8)]
...
>>> x = Blah(0xDEAD,0xBEEF,0x44,0x12)
>>> print (hex(x.a) )
0xad
>>> print (hex(x.b ))
0xef
>>> print (hex(x.c ))
0x44
>>> print (hex(x.d ))
0x12
>>>
>>> g = Blah(0x2BAD,0xBEEF,0x55,0x12)
>>> print (hex(g.a ))
0xad
>>> print (hex(g.b ))
0xef
>>> print (hex(g.c ))
0x55
>>> print (hex(g.d ))
0x12
>>>

有人知道为什么会发生这种情况吗?


阅读 46

收藏
2024-11-22

共1个答案

小能豆

你的问题出在 ctypes.Structure 的字段定义中使用了位域(bit-fields)。在 ctypes 中,位域的行为可能会和预期有所不同,尤其是在涉及位数、字节对齐以及如何存储字段时。

问题分析:

  1. 位域的使用
    你定义了 ctypes.c_uint64 字段并给定了位数(例如 6440 位)。然而,在 ctypes 中,位域通常是基于 ctypes.c_intctypes.c_uint 类型来定义的,这并不总是按照你想要的方式对齐或分配位数,尤其在平台或 Python 版本的实现中,ctypes 对位域的支持可能存在不一致。

  2. 字段类型和位数的关系
    ctypes 中,位域在内部如何存储和对齐与 Python 的 int 类型和 C 语言的实现方式有所不同。尤其在位数较少的情况下,ctypes 可能会对位进行压缩或者不完全按照预期处理。例如,给定 ctypes.c_uint64, 40 时,只有 40 位会被存储,而剩下的位可能会丢失或导致不对齐。

  3. 字段对齐
    当你使用 ctypes 定义一个包含多个不同大小字段的结构时,字段可能会根据平台的要求进行对齐,这样可能会导致某些字段的值发生意外变化。例如,在你的测试中,x.a 字段可能会显示为 0x0(没有正确存储),而其他字段则正常显示。

解决方案:

由于 ctypes 在处理位域时存在平台相关的行为,并且 Python 3.1.2 对 ctypes 位域的支持不太完善,建议使用更标准的 ctypes.Structure 方式来避免位域的复杂性,或者改用其他方法来模拟位操作。

替代方案 1:避免使用位域,改用位操作

你可以手动管理位域,并使用位运算来设置字段。这将避免 ctypes 的位域对齐问题:

import ctypes

class Blah(ctypes.Structure):
    _fields_ = [("a", ctypes.c_uint64),
                ("b", ctypes.c_uint16),
                ("c", ctypes.c_uint8),
                ("d", ctypes.c_uint8)]

    def set_values(self, a, b, c, d):
        self.a = (a & 0xFFFFFFFFFFFFFF)  # 保留最多 64 位
        self.b = b & 0xFFFF  # 保留 16 位
        self.c = c & 0xFF  # 保留 8 位
        self.d = d & 0xFF  # 保留 8 位

x = Blah()
x.set_values(0xDEAD, 0xBEEF, 0x44, 0x12)

print(hex(x.a))  # 0xDEAD
print(hex(x.b))  # 0xBEEF
print(hex(x.c))  # 0x44
print(hex(x.d))  # 0x12

替代方案 2:手动指定字节大小

如果你需要特定的位数,考虑手动设置字节大小并利用位运算进行操作:

import ctypes

class Blah(ctypes.Structure):
    _fields_ = [("a", ctypes.c_uint64),
                ("b", ctypes.c_uint64),
                ("c", ctypes.c_uint8),
                ("d", ctypes.c_uint8)]

    def __init__(self, a, b, c, d):
        self.a = a
        self.b = b
        self.c = c
        self.d = d

    def set_values(self, a, b, c, d):
        self.a = a
        self.b = b
        self.c = c
        self.d = d

x = Blah(0xDEAD, 0xBEEF, 0x44, 0x12)
print(hex(x.a))  # 0xDEAD
print(hex(x.b))  # 0xBEEF
print(hex(x.c))  # 0x44
print(hex(x.d))  # 0x12

在这种情况下,使用位域的目的是为了节省空间和内存,但实际上,ctypes 处理位域时并没有提供非常灵活的控制,尤其是在处理小尺寸字段时。

总结:

  • 位域的支持ctypes 对位域的支持并不像在 C 语言中那么直接,尤其是当你指定的位数与字段类型的大小不匹配时,可能会发生意外的对齐和丢失数据问题。
  • 手动位运算:你可以避免使用位域,转而使用 Python 中的位运算来模拟字段分配。
2024-11-22