确定结构大小,忽略填充

时间:2017-03-14 13:03:23

标签: c struct c11 packing

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

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更便携的东西。

-

修改

评论中有几个人说过我的方法很糟糕,但到目前为止还没有人提出这样做​​的原因。由于struct成员是char,因此成员之间没有陷阱表示和填充(由标准保证)。

3 个答案:

答案 0 :(得分:1)

一个核心问题是buffer的大小(假设是一个字符数组)。下面的2副本,可能是几个字节的差异。

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`

答案 1 :(得分:1)

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

  1. 将“C语法”映射到普通微型计算机硬件行为的语言系列,其方式更先于定义而非规范,但在整个20世纪80年代和90年代的大部分时间基本上是100%一致的针对普通硬件。

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

  3. 尽管C标准的作者认为要求所有实施都适合C程序所服务的所有目的是不切实际的,但在某些领域出现了一种心态,即应该考虑的唯一程序“便携式”是标准要求所有实现支持的那些。一个程序可以被一个故意反复无常的实现打破,应该(鉴于这种心态)被视为“非便携”或“错误”,即使它将从语义中受益很大,普通硬件的编译器在晚期得到一致支持20世纪,标准没有定义好的替代品。

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

答案 2 :(得分:0)

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

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

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

总的来说,您不太可能构建一个强大的,可移植的协议实现,它基于将整个消息有效负载复制到相应的结构中。至少,您可能需要在主副本之后执行特定于消息类型的修复。我建议改为咬住子弹并为每种消息类型编写适当的编组函数进出相应的网络表示。您可以更轻松地将其移植到便携式中。