我通过网络接收数据报,我想将数据复制到具有适当字段的结构(对应于消息的格式)。有许多不同类型的数据报(具有不同的字段和大小)。这是一个简化版本(实际上字段总是字符数组):
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
,因此成员之间没有陷阱表示和填充(由标准保证)。
一个核心问题是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`
如果术语“语言”指的是源文本和行为之间的映射,则名称C描述了两个语言族:
即使C标准的作者认识到要求所有实现都适合C程序所服务的所有目的是不切实际的,但在某些领域出现了一种心态,即应该被认为是“便携式”的唯一程序。是标准要求所有实现支持的那些。一个程序可以被一个故意反复无常的实现打破,应该(鉴于这种心态)被视为“非便携”或“错误”,即使它将从语义中受益很大,普通硬件的编译器在晚期得到一致支持20世纪,标准没有定义好的替代品。
因为针对某些字段(如高端数字运算)的编译器可以从假设代码不依赖于某些硬件功能中受益,并且因为标准的作者不想详细说明决定哪些实现应该被认为是合适的出于某种目的,一些编译器编写者真的不想支持试图将数据叠加到结构上的代码。这种结构可能比尝试手动解析所有数据的代码更具可读性,并且努力支持此类代码的编译器可能比手动解析所有数据的代码更容易和更有效地处理它,但由于标准允许如果编译器选择这样做,编译器会以愚蠢的方式分配结构布局,编译器编写者有一种心态,即任何试图将数据叠加到结构上的代码都应该被认为是有缺陷的。
C没有用于避免结构元件之间或结构末端的填充的标准机制。然而,许多实现提供了诸如扩展之类的东西,并且因为您似乎希望将结构布局与网络消息有效负载相匹配,所以您唯一的选择是依赖于这样的扩展。
虽然使用__attribute__((packed))
或类似工作将使您能够使用sizeof
达到您的目的,这只是一个奖励。这样做的主要目的是使结构布局与网络消息结构相匹配,以利于您提议的内存复制。如果结构是使用内部填充布局的,其中协议消息没有,那么像你提议的直接的整个消息副本根本无法工作。 sizeof
否则没有给你正确的大小只是一个更大的问题的症状。
另请注意,您也可能面临复制原始字节的其他问题。特别是,如果您打算在具有不同体系结构的计算机之间交换消息,并且这些消息包含大于一个字节的整数,那么您需要考虑字节顺序差异。如果协议设计得很好,那么它实际上指定了字节顺序。类似地,如果你传递字符数据,那么你可能需要处理编码问题(它们本身可能有自己的字节顺序考虑因素)。
总的来说,基于将整个消息有效负载复制到相应的结构中,您不可能同时构建一个强大的,可移植的协议实现。至少,您可能需要在主副本之后执行特定于消息类型的修复。我建议改为咬住子弹并为每种消息类型编写适当的编组函数进出相应的网络表示。你可以更轻松地使这个便携。