【问题标题】:Determine struct size, ignoring padding确定结构大小,忽略填充
【发布时间】:2017-03-14 13:03:23
【问题描述】:

我通过网络接收数据报,我想将数据复制到具有适当字段(对应于消息格式)的结构中。有许多不同类型的数据报(具有不同的字段和大小)。这是一个简化版本(实际上字段总是字符数组):

struct dg_a
{
    char id[2];
    char time[4];
    char flags;

    char end;
};

struct dg_a data;
memcpy(&data, buffer, offsetof(struct dg_a, end));

目前我在结构的末尾添加了一个名为end 的虚拟字段,以便我可以使用offsetof 来确定要复制多少字节。

有没有更好、更不容易出错的方法来做到这一点?我一直在寻找比使用__attribute__((packed)) 和使用sizeof 更便携的东西。

--

编辑

cmets 中有几个人说我的方法不好,但到目前为止,没有人提出原因。由于结构成员为char,因此成员之间没有陷阱表示和填充(由标准保证)。

【问题讨论】:

标签: c struct c11 packing


【解决方案1】:

一个核心问题是buffer 的大小(假设是一个字符数组)。下面的2个copy,可能有几个字节的区别。

memcpy(&data, buffer, offsetof(struct dg_a, end));  // 7 
// or
memcpy(&data, buffer, sizeof data);                 // 7, 8, 16 depends on alignment.

考虑避免这些问题,并使用与任何数据结构一样宽的buffer,并在填充传入数据之前填充/填充零。

struct dg_a {
    char id[2];
    char time[4];
    char flags;
}; // no end field

union dg_all {
 struct dg_a a;
 struct dg_b b;
 ... 
 struct dg_z z;
} buffer = { 0 };

foo(&buffer, sizeof buffer); // get data

switch (bar(buffer)) {
  case `a` {
    struct dg_a data = buffer.a;  // Ditch the memcpy
    // or maybe no need for copy, just use `buffer.a`

【讨论】:

    【解决方案2】:

    如果术语“语言”指的是源文本和行为之间的映射,那么名称 C 描述了两个语言家族:

    1. 将“C 语法”映射到普通微型计算机硬件的行为的语言家族,其方式更多地由先例而非规范定义,但在整个 1980 年代和 1990 年代的大部分实现中基本 100% 一致针对普通硬件。

    2. 符合 C 规范的所有语言系列,包括那些由故意反复无常的实现处理的语言。

    尽管 C 标准的作者认识到强制所有实现都适用于 C 程序所服务的所有目的是不切实际的,但在某些领域已经出现了一种心态,即唯一应该考虑的程序“便携式”是标准要求所有实现都支持的那些。一个可能被故意反复无常的实现破坏的程序应该(考虑到这种心态)被视为“不可移植”或“错误”,即使它会极大地受益于普通硬件编译器在后期一致支持的语义20 世纪,标准没有定义好的替代品。

    因为针对某些领域(如高端数字运算)的编译器可以从假设代码不依赖某些硬件功能中受益,并且因为标准的作者不想深入了解决定应该采用什么实现的细节被认为适合什么目的,一些编译器编写者真的不想支持试图将数据覆盖到结构上的代码。这样的结构可能比试图手动解析所有数据的代码更具可读性,并且努力支持此类代码的编译器可能比手动解析所有数据的代码更容易和更有效地处理它,但由于标准允许如果编译器选择这样做,他们会以愚蠢的方式分配结构布局,编译器编写者有一种心态,即任何试图将数据覆盖到结构上的代码都应该被认为是有缺陷的。

    【讨论】:

      【解决方案3】:

      C 没有标准机制来避免结构元素之间或结构末尾的填充。但是,许多实现都提供了扩展之类的东西,并且由于您似乎希望将结构布局与网络消息有效负载相匹配,您唯一的选择就是依赖这样的扩展。

      虽然使用__attribute__((packed)) 或类似作品可以让您将sizeof 用于您的目的,但这只是一个奖励。这样做的主要目的是将结构布局与网络消息结构相匹配,以利于您建议的内存复制。如果结构是在协议消息没有内部填充的情况下布置的,那么像您建议的那样直接的、完整的消息副本根本无法工作。 sizeof 否则不会给你正确的大小只是更大问题的症状。

      另请注意,您也可能在复制原始字节时遇到其他问题。特别是,如果您打算在不同架构的机器之间交换消息,并且这些消息包含大于一个字节的整数,那么您需要考虑字节顺序的差异。如果协议设计得很好,那么它实际上指定了字节顺序。同样,如果您要传递字符数据,那么您可能需要处理编码问题(这些问题本身可能有自己的字节顺序考虑)。

      总体而言,您不太可能同时将整个消息有效负载复制到相应的结构中,从而构建一个健壮、可移植的协议实现。至少,您可能需要在主副本之后执行特定于消息类型的修复。我建议改为咬紧牙关,为每种消息类型编写适当的编组函数进出相应的网络表示。你会更容易使这个便携。

      【讨论】:

        猜你喜欢
        • 2018-10-28
        • 2018-02-11
        • 2012-08-22
        • 2016-09-18
        • 2020-07-26
        • 2020-05-15
        • 2011-02-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多