【问题标题】:memcpy to union isn't working as expected到 union 的 memcpy 没有按预期工作
【发布时间】:2017-09-15 11:29:30
【问题描述】:

我正在开发一些嵌入式软件,并试图让事情变得更加灵活。我希望能够做的一件事是更改结构并在整个应用程序的其余部分更改数据处理方式。

我注意到我可以将 一些 数据从有效负载复制到联合中,但由于某种原因不能全部复制。这是我正在做的事情:

union ConcentratorPacket {
 struct PacketHeader header;
 struct NetworkJoinReqPacket networkJoinReqPacket;
};

static union ConcentratorPacket latestRxPacket;

地点:

struct PacketHeader {
 uint32_t sourceAddress;
 uint8_t packetType; 
};

struct NetworkJoinReqPacket{
 struct PacketHeader header;
 uint8_t maxDataLen;
};

稍后我想将接收到的数据包中的数据移动到这个联合中的相关结构中,所以我想这样做:

memcpy(&latestRxPacket.networkJoinReqPacket, rxPacket->payload, sizeof(latestRxPacket.networkJoinReqPacket));

rxPacket->payload 是一个 uint8_t 数组,以正确的顺序传递。

我看到的是这个方法很好地填充了 packetHeader,但是 maxDataLen 没有采用正确的值。事实上,它所取的值是有效载荷[8] 而不是有效载荷[5]。

我发现解决此问题的唯一方法是直接分配 maxDataLen,但如果结构因任何原因发生更改,则需要在每个地方进行更改,因此 memcpy 比这个更可取:

 memcpy(&latestRxPacket.networkJoinReqPacket.header, rxPacket->payload, sizeof(latestRxPacket.networkJoinReqPacket.header));
 latestRxPacket.networkJoinReqPacket.maxDataLen = rxPacket->payload[5];

我认为我所看到的表明 memcpy 将 maxDataLen 视为 uint32,这是正确的,但我不知道如何避免这种情况。

这很奇怪,因为我在其他地方做了类似的事情并且效果很好,但唯一的区别是 maxDataLen 的等效项是 uint32,而不是 uint8。

任何帮助或指导将不胜感激。

【问题讨论】:

  • 我猜这与结构填充有关。如果你使用你的编译器编译指示或类似的东西来打包没有填充的结构怎么办?
  • 啊哈,你成功了!对结构还不够熟悉,无法了解填充/打包。做了一些谷歌搜索,发现我的编译器支持 attribute__((__packed)),所以我把 PacketHeader 放在前面,这一切都不同了。非常感谢!
  • 强制警告:应避免滥用结构用于序列化目的,因为对字节序和填充的控制是有限的。建议单独处理每个结构成员。
  • @user694733,你能详细说明一下吗?
  • 您的问题是该问题的一个典型例子:要解决它,您必须求助于编译器特定的功能,这限制了代码的可移植性。我引用我不久前对类似帖子的评论:“您可以从阅读this wikipedia articlethis SO question 开始。网上有很多关于这方面的信息。”

标签: c struct embedded unions memcpy


【解决方案1】:

正如一些程序员老兄指出的那样,它与结构填充/打包有关。默认情况下,我在 Code Composer Studio 中使用的 TI 编译器似乎会填充结构以将它们与 32 位内存对齐。

幸运的是,__attribute__((__packed__)) 得到了编译器的支持。

只需更改PacketHeader 的定义即可解决问题:

struct __attribute__((__packed__)) PacketHeader {
  uint32_t sourceAddress;
  uint8_t packetType;
};

【讨论】:

  • 使用结构体来表示数据协议通常不是一个好主意。编译器使用填充是有原因的——它提供了更有效的访问。
【解决方案2】:

使用“打包”结构时要小心 - 很容易获得不对齐的访问,这可能是非法的或仅仅是低效的,具体取决于目标 cpu 的详细信息。考虑重新排列结构类型以避免“打包”的需要。

当你为这类事情使用结构时,我建议在编译器上使用“-Wpadded”。这将让编译器告诉您何时将填充添加到结构中。如果您期望填充,请显式添加(例如“uint8_t dummy[3];”)。

同时使用 _Static_assert 来检查你的结构是你期望的大小。您的目标是让不匹配产生编译时错误,而不是等待测试和调试。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-03
    • 2021-10-19
    • 2020-03-18
    • 2012-06-14
    • 2014-11-15
    • 1970-01-01
    相关资源
    最近更新 更多