【问题标题】:How to fill in struct paddings in C/C++?如何在 C/C++ 中填充结构填充?
【发布时间】:2015-04-25 00:07:33
【问题描述】:

假设我有以下结构:

typedef struct {
    int mID;
    struct in_addr mIP;
    size_t dataSize;
    // Another structure
    fairness_structure str;
    bool ack;
    bool stability;
    bool stop_message;
}HeaderType;

如您所知,结构的大小会因其对齐方式而有所不同。如何用一些数据填充字段之间的填充,比如用零?

【问题讨论】:

  • 你希望通过填充padding达到什么目的?
  • 您是否在问如何在对齐填充内隐藏数据?对齐会自动补零。
  • @RPGillespiem,谢谢,请您提供官方文档以确认。
  • 这没什么好证明的,这个结构体就是sizeof(HeaderType)的一块内存,如果你用零初始化,那么即使填充也是零。和初始化数组是一回事。
  • @MikeSeymour 在 C99 中,类型只有在具有静态存储时才被零初始化,否则它是未定义的。 stackoverflow.com/a/21152256/4591872

标签: c++ c struct padding


【解决方案1】:

只需用 memset 初始化结构,padding 也会被填充。

memset(&mystruct, 0, sizeof(HeaderType));

如果您真的只想填充焊盘,可以将指针转换为char* 并进行算术运算。但在这种情况下,您必须知道编译器如何填充结构,或者使用#pragma pack 自己强制执行。 您可以使用offsetof() 宏来获取结构成员的偏移量。

char *off = (char *)&mystruct + offsetof(HeaderType, ack);
char *pad_start = off + sizeof(mystruct.ack);
char *pad_end   = (char *)&mystruct + offsetof(HeaderType, stability);

睡前阅读:The Lost Art of C Structure Packing

【讨论】:

  • 谢谢,有没有知道编译器是如何填充结构的?
  • 视情况而定。阅读文章所附链接。
  • 最好使用sizeof mystruct,这样你就不会冒险写错类型名称。
  • 这是一把双刃剑。当数组后来被重构为指针时,我看到了很多错误,例如:char mystr[] = "test"; use_it(mystr, sizeof(mystr)) 然后const char *mystr = "test"; use_it(mystr, sizeof(mystr))。当你改变一个结构时,你可能会改变一个名字,所以编译器会告诉你你也应该修复你的sizeof()。但这取决于个人喜好,恕我直言,我建议不要这样做。
  • 在 arrays 上尝试此操作时要小心。请记住(正如 esr 文档的链接所提到的),结构的 array 可能具有不同的 stride 大小;所以除了结构本身的字段之间的填充之外,一个结构和下一个结构之间也可能存在填充。对于数组,memset 和 offsetof 技巧都不起作用。
【解决方案2】:

控制填充位和字节的内容似乎不是很有用。但是,如果您使用单个 write 或 fwrite 调用将结构的内容写入文件,您可能关心填充,并且可能希望确保它们始终具有一致的值,最好是 0。当您从文件中读回内容时,这并不重要,而是为了使文件内容可预测和可重现。正是由于这个原因,一些开发工具会在对象或可执行文件中产生不可预测的内容,这使得从源代码重建和检查签名变得非常困难。

所以如果你真的需要这个,你需要一个简单便携的方法。

坏消息是 C 标准对此没有通用解决方案。

该标准对填充字节和位的内容的唯一保证是用于静态存储的未初始化结构。在这种情况下(在托管环境中),填充保证为零。实际上,初始化结构也是如此,因为编译器编写者这样做很简单。

具有自动存储功能的本地结构怎么样?如果它们未初始化,则字段和填充内容都是不确定的。如果您只是使用memset(&s, 0, sizeof(s)) 清除字节,则填充将被清除,您可以开始修改结构成员......再次坏消息:C 标准描述为 未指定的行为 在结构或联合中存储值时填充字节 (6.2.6.1)。

换句话说,将值存储在结构成员中会对填充位和字节的内容产生副作用。允许编译器生成执行此操作的代码,这样做可能更有效。

Marek 描述的方法超越了简单的memset,使用起来非常麻烦,尤其是如果您有位域。实际上,在手动初始化字段之前清除结构似乎是实现该目的的最简单方法,而且我还没有看到编译器利用标准对填充字节的宽大处理。如果您按值传递结构,则所有赌注都将被取消,因为编译器可能会生成不复制填充的代码。

作为结论:如果你使用局部结构,在使用前用memset清除它们,不要通过值传递它们。没有保证填充会保持 0 值,但这是你能做的最好的。

【讨论】:

  • 非常感谢@chqrlie(查理)
  • 我同意该方法非常繁琐,我再次将其视为一个问题,而不是真正的用例。在现实世界中,即使是打包结构,酸洗也会导致许多其他可移植性问题,例如整数字节序。结构应始终按成员编组。
猜你喜欢
  • 1970-01-01
  • 2023-04-03
  • 2011-10-21
  • 2017-06-08
  • 2015-09-04
  • 1970-01-01
  • 2014-10-12
  • 2015-07-02
  • 2010-10-24
相关资源
最近更新 更多