ctypes。具有位字段的结构未正确设置值

问题描述 投票:0回答:2

我在C中有以下内容

#include <stdio.h>

struct Demo {
    char f1:8;
    int f2:21;
    char f3:2;
    char f4:1;
};

int main()
{
    struct Demo d = {1, 15, 3, 1};
    printf("%ld\n", sizeof(d));
    printf("f1=%d|f2=%d|f3=%d|f4=%d", d.f1, d.f2, d.f3, d.f4);

    return 0;
}

打印

4
f1=1|f2=15|f3=-1|f4=-1

我一直在用

ctypes.Structure
的位域,并实现了上面的结构。

我的问题是,无论我做什么,我都无法为我的字段设置值。

我在 Python 3.8.10 和 3.9.9 中有以下内容:

from ctypes import Structure, c_byte, c_int, sizeof
class Demo(Structure):
  _fields_ = [
    ('f1', c_byte, 8),
    ('f2', c_int, 21),
    ('f3', c_byte, 2),
    ('f4', c_byte, 1)
  ]

print(sizeof(Demo))

d = Demo.from_buffer_copy(b'\x01\x0f\x00\xe0')
print(f'{d.f1=}|{d.f2=}|{d.f3=}|{d.f4=}')

打印

1
4
d.f1=1|d.f2=15|d.f3=0|d.f4=0

因此 C 和 Python 定义是等效的,但它们设置的值不是。

字节对象 (

b'\x01\x0f\x00\xe0'
) 是 C 结构的二进制表示,具有以下值(如上所示):

  • f1
    :1
  • f2
    :15
  • f3
    :-1(3,溢出使它成为-1)
  • f4
    :-1(1,溢出使它成为-1)

手动设置,或者

from_buffer_copy
,一事无成;
f3
f4
保持 0.

有人可以帮助我了解如何设置

f3
f4
,并以相应字段大小的位形式访问它们的值。

注意事项

通过搜索 https://bugs.python.org,以及过去的 GitHub 问题,我终于遇到了 #59324(11 岁)和 #97588,它们涵盖了与我相同的问题,没有追索权。

虽然似乎有一个待定的 PR 可能会在即将发布的 Python 版本中解决这个问题。

python structure ctypes bit-fields
2个回答
1
投票

问题是没有在整个 32 位字段中使用相同的类型。当类型发生变化时,将开始一个新的包装并在类型大小的边界上对齐。请参阅下面的注释以及原始和正确结构的大小(以字节为单位)。请注意,

bytes()
可用于
Structure
实例以查看构造后的结果字节。

import ctypes as ct

class Demo1(ct.Structure):
    _fields_ = (('f1', ct.c_byte, 8),  # 1 byte started
                ('f2', ct.c_int, 21),  # 1 int started (3 bytes padding, then 4 bytes)
                ('f3', ct.c_byte, 2),  # 1 byte started
                ('f4', ct.c_byte, 1))  # included in previous byte
                # 3 bytes structure padding so 'f2' will be aligned in an array of Demo1
    def __repr__(self):
        return f'Demo1(f1={self.f1}, f2={self.f2}, f3={self.f3}, f4={self.f4})'

class Demo2(ct.Structure):
    _fields_ = (('f1', ct.c_int32, 8),  # one int started
                ('f2', ct.c_int32, 21), # included in previous int
                ('f3', ct.c_int32, 2),  # included in previous int
                ('f4', ct.c_int32, 1))  # included in previous int
    def __repr__(self):
        return f'Demo2(f1={self.f1}, f2={self.f2}, f3={self.f3}, f4={self.f4})'

print(f'{ct.sizeof(Demo1)=}')
print(f'{ct.sizeof(Demo2)=}')

d1 = Demo1(1, 15, -1, -1)  # Set the fields in the constructor
print(d1, bytes(d1))       # Resulting structure and bytes.

d2 = Demo2.from_buffer_copy(b'\x01\x0f\x00\xe0')
print(d2, bytes(d2))
d2 = Demo2(1, 15, -1, -1)
print(d2, bytes(d2))

输出:

ct.sizeof(Demo1)=12
ct.sizeof(Demo2)=4
Demo1(f1=1, f2=15, f3=-1, f4=-1) b'\x01\x00\x00\x00\x0f\x00\x00\x00\x07\x00\x00\x00'
Demo2(f1=1, f2=15, f3=-1, f4=-1) b'\x01\x0f\x00\xe0'
Demo2(f1=1, f2=15, f3=-1, f4=-1) b'\x01\x0f\x00\xe0'

0
投票

如前所述;为位域使用一致的类型是一种有效的解决方法,因为您不受任何限制。

但鉴于这是一个解释器问题,即将发布的 PR 应该会很快解决这个问题,并且也将向后移植到 v3.11。

所以对于任何登陆这里寻找解决方案的人:

  • 在位域值定义中使用相同的类型,
  • 升级到v3.11或更高版本,或者
  • 如果您对 C 有足够的经验,请参考 PR 并编辑源代码,以创建自定义构建。
© www.soinside.com 2019 - 2024. All rights reserved.