【问题标题】:How to get precise struct size using Python 'struct' module如何使用 Python 'struct' 模块获得精确的结构大小
【发布时间】:2013-09-02 08:30:24
【问题描述】:

我正在尝试通过 TCP 从 C(实际上是 Obj-C,但面向对象不在此列)客户端向 python 服务器发送消息。现在我首先发送一个unsigned short,其中包含消息大小,然后是消息,它是一个C 结构。我想在包的末尾附加一个动态字符串,所以我决定使用结构体大小将包一分为二,但问题开始了。

问题是,要么我做错了,要么 Python 的 struct 库大小 + 填充计算存在错误。

Python struct 似乎可以正确解析填充。例如,对于这个结构:

struct.Struct("H I").size == 8

与此结构的sizeof 返回值匹配:

#include <stdio.h>

typedef struct {
        unsigned short a;
        unsigned int b;
} test;

int main() {
        printf("%ld\n", sizeof(test));
        return 0;
}

$ gcc test.c 
$ ./a.out
8

但是对于某些结构定义,我并不总是得到相同的结果。例如,在这种情况下:

struct.Struct("H 5s").size == 7

typedef struct {
    unsigned short a;
    char b[5];
} test;
sizeof(test) == 8

我在某处读到编译器可能会填充一个结构,以确保在数组中使用结构时可以正确访问内存。我不确定是否是这种情况(似乎是),但如果是这样,我不明白为什么这个结构没有填充到 8 个字节(假设是 4 个字节的打包):

struct.Struct("H 4s").size == 6

typedef struct {
    unsigned short a;
    char b[4];
} test;
sizeof(test) == 6

所以,澄清一下,我的问题是如何在 Python 中获得给定结构的精确大小,因为它没有应用最终填充。


我尝试过的:

手动添加最终填充大小:

real_struct_size = self._struct.size + self._struct.size % 4

当然,这不起作用,因为单个成员结构不会添加填充,并且您可以在最后一种情况下看到它也不适用于小型结构(无符号短+字符[4])。 (也许我在这里过度简化了问题。也许这与小结构无关,而是与另一个我无法识别的因素有关。)

然后我打开了 Python 的 struct 库,看看我怎样才能知道需要多少个参数,所以我可以问它是否为 1,然后避免最后的填充,但是无法访问 @ 的 s_len 属性987654330@(参见 Python-2.7.5/Modules/_struct.c:48),这是存储打包参数数量的地方。

因此,作为一种解决方法,我在数据包的开头放置了一个偏移值,以了解额外/动态字符串的开始位置。

但我认为这里有一个错误(我的或来自 Python 的 struct 库)。无论哪种方式,如果是我,我真的需要知道我做错了什么,或者如果它是 Python 的库,我想报告这个问题。如果有人能帮助我查明真相,我将不胜感激。

所以,提前谢谢!抱歉发了这么长的帖子:)

【问题讨论】:

  • 你试过使用struct.calcsize()函数吗?
  • @martineau 它给出的结果与Struct.size(在我的机器上)相同。事实上Struct.size 的文档字符串似乎是calcsize 的文档字符串的副本。我不是 C 的专家,但我相信该标准并不能保证 structs 的大小,所以 python cannot 在任何情况下都可以可靠地计算它。不同的编译器会添加不同的填充(我相信主要取决于架构)。
  • 文档还说“要将结构的末尾与特定类型的对齐要求对齐,请以该类型的代码结束格式,重复计数为零”,所以也许你可以做这样的事情。还要确保使用两种本机类型(@=)之一作为格式字符串的前缀,否则将不会添加任何填充。
  • 嗯,这不是自动的,而是更好的解决方法,我会试一试,谢谢!关于前缀,例如“如果第一个字符不是其中之一,则假定为'@'。”我会在这里尝试更新:)
  • @martineau 遗憾的是添加 0 不会影响大小计算。还是谢谢!

标签: python c python-2.7 struct


【解决方案1】:

简短的回答:你不能。 struct 模块仅通过重用基本类型的一些符号来与 C 类型相关,以方便程序员。所有与填充相关的修复都会在您将代码移动到不同平台、由另一个编译器编译的代码或其他任何东西时中断。

获取结构(c-struct)大小的唯一方法是从 C 中引用它并使用编译器编译该代码。您可以使用单线像

return PyInt_FromLong(sizeof(mystruct));

长答案:实现一些#include 适当类型的包装器代码,将它们写入内存并传递它们(作为不透明对象)。您可以实现 bufferview 协议,以便将其直接传递给 socket.send()

【讨论】:

  • 不同意。模块本身已经依赖于编译器imgur.com/TGcrAkp,并且编译器在编译时根据配置定义填充行为:imgur.com/B6YD2Cw——因此,即使使用当前应用的类型填充,您也依赖编译器配置来打包或解包结构.无论如何,实现 bufferview 协议听起来是个好主意,我会试一试。谢谢。
【解决方案2】:

要将结构的末尾对齐到对齐要求,我们只需要找到最大的整数类型。像这样的:

def c_sizeof(s):
    # Types sorted in size order
    size_map = "cbB?hHiIlLqQfd"
    # Filter out chars in s that not in size_map.
    # The default align char ("c") in case filtered list is empty.
    chars = filter(lambda x: x in size_map, s) + "c"
    # Largest index and its char in size_map gives the align char
    align_char = size_map[max([size_map.index(x) for x in chars])]
    # Using native prefix to calculate alignment between fields
    return struct.calcsize("@{0}0{1}".format(s, align_char))

并运行一些测试

print c_sizeof("cci"), c_sizeof("cic"), c_sizeof("H5s")

生产

8 12 8

【讨论】:

    猜你喜欢
    • 2019-04-19
    • 1970-01-01
    • 2018-10-28
    • 2020-11-27
    • 2012-11-07
    • 1970-01-01
    • 2019-06-01
    • 1970-01-01
    • 2020-10-29
    相关资源
    最近更新 更多