我正在编写一个模块来压缩要传递给 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 >>>
有人知道为什么会发生这种情况吗?
你的问题出在 ctypes.Structure 的字段定义中使用了位域(bit-fields)。在 ctypes 中,位域的行为可能会和预期有所不同,尤其是在涉及位数、字节对齐以及如何存储字段时。
ctypes.Structure
ctypes
位域的使用: 你定义了 ctypes.c_uint64 字段并给定了位数(例如 64 或 40 位)。然而,在 ctypes 中,位域通常是基于 ctypes.c_int 或 ctypes.c_uint 类型来定义的,这并不总是按照你想要的方式对齐或分配位数,尤其在平台或 Python 版本的实现中,ctypes 对位域的支持可能存在不一致。
ctypes.c_uint64
64
40
ctypes.c_int
ctypes.c_uint
字段类型和位数的关系: 在 ctypes 中,位域在内部如何存储和对齐与 Python 的 int 类型和 C 语言的实现方式有所不同。尤其在位数较少的情况下,ctypes 可能会对位进行压缩或者不完全按照预期处理。例如,给定 ctypes.c_uint64, 40 时,只有 40 位会被存储,而剩下的位可能会丢失或导致不对齐。
int
ctypes.c_uint64, 40
字段对齐: 当你使用 ctypes 定义一个包含多个不同大小字段的结构时,字段可能会根据平台的要求进行对齐,这样可能会导致某些字段的值发生意外变化。例如,在你的测试中,x.a 字段可能会显示为 0x0(没有正确存储),而其他字段则正常显示。
x.a
由于 ctypes 在处理位域时存在平台相关的行为,并且 Python 3.1.2 对 ctypes 位域的支持不太完善,建议使用更标准的 ctypes.Structure 方式来避免位域的复杂性,或者改用其他方法来模拟位操作。
你可以手动管理位域,并使用位运算来设置字段。这将避免 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
如果你需要特定的位数,考虑手动设置字节大小并利用位运算进行操作:
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 处理位域时并没有提供非常灵活的控制,尤其是在处理小尺寸字段时。