【问题标题】:Struct w/ bit field: CPython and C padding differs带/位域的结构:Python 和 C 填充不同
【发布时间】:2018-05-11 11:34:00
【问题描述】:

考虑以下程序:

#include <stddef.h>
#include <stdio.h>

typedef struct
{
    unsigned        bit:1;
    unsigned char   str[8];
} test;

#pragma pack(1)
typedef struct
{
    unsigned        bit:1;
    unsigned char   str[8];
} test_pack;

int main(int argc, char **argv) {

    printf("%3lu str offset\n", offsetof(test, str));
    printf("%3lu total\n", sizeof(test));

    printf("%3lu str_pack offset\n", offsetof(test_pack, str));
    printf("%3lu total\n", sizeof(test_pack));

    return 0;
}

哪个输出

  1 str offset
 12 total
  1 str_pack offset
  9 total

在我的带有 GCC 4.8.4 的 Ubuntu 14.04.3 系统上。

(我认为)等效的 Python 程序,

#!/usr/bin/python3

from ctypes import *

class Test(Structure):
    _fields_ = [
        ('bit', c_uint, 1),
        ('str', c_ubyte * 8),
    ]

class TestPacked(Structure): 
    _pack_ = 1
    _fields_ = [
        ('bit_p', c_uint, 1),
        ('str_p', c_ubyte * 8),
    ]

if __name__ == "__main__":
    print("%3lu str offset" % Test.str.offset)
    print("%3lu total" % sizeof(Test))

    print("%3lu str_p offset" % TestPacked.str_p.offset)
    print("%3lu total_p" % sizeof(TestPacked))

输出

  4 str offset
 12 total
  4 str_p offset
 12 total_p

在同一系统上,运行 Python 3.4.0。

据我所知,位域应该占 1 位。 C 和 Python 都将其填充为 32 位,为结构添加 3 个字节的空白空间,以便更好地对齐。

但是,Python 将填充放在字符串之前,而 C 将其放在之后。

此外,对于 #pragma pack(1),C 会删除填充,但 Python 不会。

  • 我可以让 python 在字符串之后而不是之前添加 3 个字节的填充吗?
  • 如果做不到这一点,python 是否可以正确打包结构以便它们对齐?

不需要通过网络协议或任何东西跨不同系统工作...只是试图让位在一个系统上排队,即使我必须以某种方式重新配置它。谢谢!

【问题讨论】:

  • 我解决了一个问题并创建了另一个问题...我将c_uint(4 个字节)作为python 中位字段的类型,如果我将其更改为c_uint8(1 个字节)它行为与具有 pragma pack(1) 的 C 版本相同。这回答了我的第二个问题,但我仍然想知道如何保留填充,但按照 GCC 的方式将其移动到字符串之后。在类中添加_pack_ = 4 似乎没有任何效果。
  • 在这种情况下不要使用位域。让您的所有字段都属于某种可寻址类型。使用按位运算(例如按位和&amp;、移位&lt;&lt;| 等...)

标签: python padding ctypes memory-alignment


【解决方案1】:

这可能是这个众所周知的问题的重复: https://bugs.python.org/issue29753 几个月前已经验证了一个补丁,但尚未合并到官方资源中。

【讨论】:

    【解决方案2】:

    问题在于位域的实现明确依赖于实现。 C 标准(C11 的参考 n1256 草案)在 6.7.2.1 结构和联合说明符中说:

    ...
    §4 位域的类型应为 _Bool 的合格或不合格版本,带符号 int、unsigned int 或其他一些实现定义的类型。
    ...
    9 位域被解释为有符号或无符号整数类型,由指定的 位数

    好的,你想要一个使用单个位的无符号类型,但下一段说(强调我的):

    10 实现可以分配任何足够大的可寻址存储单元来保存位域。

    这里一个字节就足够了,所以 gcc 将使用一个字节。但是 32 位模式下的 MSVC 将使用 4 个字节,C 程序的输出将是:

      4 str offset
     12 total
      4 str_pack offset
     12 total
    

    正是 CPython 实现输出的内容

    这意味着ctypes 的实施者选择在此处遵循 MSVC。除了大多数示例使用 Windows 的事实之外,模块文档中的两句话给出了提示:

    • 16.16.1.10 中的警告。结构和联合¶

    警告:ctypes 不支持将具有位域的联合或结构按值传递给函数。虽然这可能适用于 32 位 x86,但库不保证在一般情况下也能正常工作。

    下一段在 16.16.1.11 中说明。结构/联合对齐和字节顺序

    默认情况下,Structure 和 Union 字段的对齐方式与 C 编译器的对齐方式相同...这也是 #pragma pack(n) 在 MSVC 中所做的

    问题在于ctypes 模块的实现者选择了一个约定——他们不能直接依赖编译器,因为位域的大小必须是一个常数——与平台无关。您可能认为这是非 MSVC 实现的问题并填写错误报告,但我也认为 ctypes 模块主要用于 Windows。

    因此,能够在此处处理您的 C 结构的唯一方法是显式强制位字段的基础类型并将第一个字段声明为无符号字节,并最终指定显式填充:

    class Test(Structure):
        _fields_ = [
            ('bit', c_byte),
            ('str', c_ubyte * 8),
            ('', c_ubyte * 3),      # explicit padding
        ]
    
    class TestPacked(Structure): 
        _fields_ = [
            ('bit_p', c_ubyte), 
            ('str_p', c_ubyte * 8),
        ]
    

    但这只是一种解决方法,因为这里 bitbit_p 是纯字节,而 C 代码要求只使用 1 位,其他 7 位是填充位。

    【讨论】:

      【解决方案3】:

      AFAICT,C 中位字段的规则非常模糊(并且取决于编译器/ABI),以至于您永远无法确定它们将如何被填充或对齐(尤其是在编译器特定的打包 pragma 开始发挥作用时)。您可以通过自己显式定义额外的填充位字段片段(使总位数等于底层数据类型)来使它们更便携,但这一切都将是骇人听闻的。由于无论如何这都是骇客,解决您如何保留填充的问题,但按照gcc 的方式将其移动到字符串之后非常容易,请手动定义填充:

      class Test(Structure):
          _fields_ = [
              ('bit', c_uint8, 1),
              ('str', c_ubyte * 8),
              ('', c_ubyte * 3), # Unnamed fields apparently work, go figure
          ]
      
      >>> sizeof(Test)
      12
      >>> Test.str.offset
      1
      

      【讨论】:

      • 这并没有让它们更便携,因为规则非常模糊,你甚至无法确定你的位域会按照你声明它们的顺序出现。当你关心表示时,位域不是你的朋友。它们只是仅对本地对象提供便利。
      猜你喜欢
      • 2014-07-01
      • 2015-09-04
      • 1970-01-01
      • 1970-01-01
      • 2012-11-14
      • 1970-01-01
      • 1970-01-01
      • 2021-05-28
      • 2013-08-24
      相关资源
      最近更新 更多