ctypes.struct(packed)中的sizeof与C中的压缩结构不匹配

时间:2016-11-06 21:14:39

标签: c python-3.x struct ctypes bit-fields

我在C中有一个打包结构,我想在Python中解析。我注意到C(使用GCC 4.9.2)和Python 3.4.2中的ctypes库之间的运算符sizeof返回的结构大小与位域有所不同。

以下C代码按预期打印 5

#include <stdio.h>
#include <stdint.h>

typedef struct __attribute__((packed)) {
    uint32_t ch0 : 20;
    uint32_t ch1 : 20;
} pkt_t;

int main(){
    printf("sizeof(pkt_t): %d\n", sizeof(pkt_t));
    return 0;
}

虽然Python中的(相同)代码打印 8

import ctypes

class Packet(ctypes.LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('ch0', ctypes.c_uint32, 20),
        ('ch1', ctypes.c_uint32, 20),
    ]   

print(ctypes.sizeof(Packet()))

看起来_pack_ = 1相当于C中的__attribute__((aligned(1))),而不是__attribute__((packed, aligned(1))),它会使结构尽可能紧密地打包。有没有办法为ctypes结构启用packed属性?

2 个答案:

答案 0 :(得分:1)

ctypes文档https://docs.python.org/3/library/ctypes.html#structure-union-alignment-and-byte-order明确指出

默认情况下,“结构”和“联合”字段的对齐方式与 C编译器可以做到。可以通过以下方式覆盖此行为: 在子类定义中指定 pack 类属性。这个 必须设置为正整数并指定最大对齐 为领域。这是在MSVC中#pragma pack(n)的作用

这意味着它们不是引用gcc的内容,而是引用MSVC #pragma pack。参见:https://docs.microsoft.com/en-us/cpp/preprocessor/pack?view=vs-2019

n

(可选)指定用于打包的值(以字节为单位)。如果 未为模块设置编译器选项/ Zp,默认值 当n为8时。有效值为1、2、4、8和16。 成员位于n的倍数或倍数的边界上 成员大小中的较小者。

因此,观察到此行为的原因之一是MSVC与gcc的编译器详细信息。

gcc文档https://gcc.gnu.org/onlinedocs/gcc-4.9.4/gcc/Type-Attributes.html#Type-Attributes(很抱歉,gcc-4.9.2的文档在gcc页面上不可用),另一方面明确指出:

此属性(打包),附加到结构或联合类型定义中,用于指定 该结构的每个成员(零宽度位域除外) 或放置联合以最小化所需的内存。当连接到 一个枚举定义,它表示最小的整数类型 应该使用。

在这种情况下,它们对边界要求一无所知,而内存占用量则最小。如果有必要在字节边界上放置一个位字段,则可能是另一个讨论的主题。 对于我来说,为什么__attribute__((aligned(1)))实际按预期设置结构似乎是合乎逻辑的并且与我一致,因为它明确地强制执行结构成员的字节对齐,而通过指定__attribute__((packed))似乎不是这种情况。在第一种情况下,gcc编译器因此也对位字段强制执行字节对齐。

总结:

  • MSVC #pragma pack(n)指定要放置在内存偏移量为n的倍数的struct成员的对齐方式
  • gcc的__attribute__ ((aligned (n)))要求对齐结构(成员)
  • gcc的__attribute__ ((__packed__))请求,用于最大程度地减少内存占用,即使这意味着位域的字节边界已越过
  • gcc的packed和aligned属性的组合也要求减少内存占用,但也存在强制执行对齐的限制。这样就不会越过位域字节边界

答案 1 :(得分:0)

我认为对齐不是这里的问题,因此在python中将对齐设置为1并没有达到预期的效果。

问题是C中的位域填充了给定的类型,但没有越过边界。 uint32_t具有32位,每个成员需要20位。因此,每个成员将放入一个单独的uint32_t中,总共8个字节。

打包属性以某种方式导致gcc忽略该规则并总共使用40bit。

另一方面,Python ctypes遵循该规则并获得8。