【问题标题】:using memcpy for structs将 memcpy 用于结构
【发布时间】:2013-10-16 14:50:02
【问题描述】:

在结构上使用 memcpy 时遇到问题。

考虑以下结构

struct HEADER
{
    unsigned int preamble;
    unsigned char length;
    unsigned char control;
    unsigned int destination;
    unsigned int source;
    unsigned int crc;
}

如果我使用 memcpy 将数据从接收缓冲区复制到此结构,则复制是可以的,但如果我将结构重新声明为以下内容:

struct HEADER
{
    unsigned int preamble;
    unsigned char length;
    struct CONTROL control;
    unsigned int destination;
    unsigned int source;
    unsigned int crc;
}

struct CONTROL
{
    unsigned dir : 1;
    unsigned prm : 1;
    unsigned fcb : 1;
    unsigned fcb : 1;
    unsigned function_code : 4;
}

现在,如果我使用与以前相同的 memcpy 代码,则可以复制前两个变量(前导码和长度)。控件完全乱了,最后三个变量上移了一个,也就是 crc = 0,source = crc,destination = source...

有人对我有什么好的建议吗?

【问题讨论】:

  • 您还应该显示您使用的memcpy 代码。
  • 你确定,在 HEADER 改变后,接收到的缓冲区有完全相同的变化吗?

标签: c struct memcpy


【解决方案1】:

中间加上control,你知道接收缓冲区中的格式是正确的吗?

无论如何,你的问题是位域在这里是错误的工具:你不能依赖于内存中的布局,尤其是你为序列化表单选择的完全相同的布局。

尝试直接将结构复制到外部存储或从外部存储复制几乎从来都不是一个好主意;你需要适当的序列化。编译器可以在结构的字段之间添加填充和对齐,使用位域会使情况变得更糟。不要这样做。

实现适当的序列化/反序列化功能:

unsigned char * header_serialize(unsigned char *put, const struct HEADER *h);
unsigned char * header_deserialize(unsigned char *get, struct HEADER *h);

遍历结构并根据需要读取/写入尽可能多的字节(可能针对每个字段):

static unsigned char * uint32_serialize(unsigned char *put, uint32_t x)
{
    *put++ = (x >> 24) & 255;
    *put++ = (x >> 16) & 255;
    *put++ = (x >> 8) & 255;
    *put++ = x & 255;
    return put;
}

unsigned char * header_serialize(unsigned char *put, const struct HEADER *h)
{
    const uint8_t ctrl_serialized = (h->control.dir << 7) |
                                    (h->control.prm << 6) |
                                    (h->control.fcb << 5) |
                                    (h->control.function_code);

    put = uint32_serialize(put, h->preamble);
    *put++ = h->length;
    *put++ = ctrl_serialized;
    put = uint32_serialize(put, h->destination);
    put = uint32_serialize(put, h->source);
    put = uint32_serialize(put, h->crc);

    return put;
}

请注意,这需要明确说明序列化数据的字节序,这是您始终应该关心的事情(我使用了大字节序)。假设使用了 struct 版本,它还显式构建了 control 字段的单个 uint8_t 版本。

另请注意,您的CONTROL 声明中有错字; fcb 出现两次。

【讨论】:

  • 你能解释一下序列化函数的作用吗?以前从未使用过类似的东西...
  • 查看指针内存(指向 struct 的指针)时,数据正确地位于那里,但查看手表时,数据是乱码...
  • @user1244472 在序列化函数中,您应该将每个结构成员转换为字节。
【解决方案2】:

使用struct CONTROL control; 而不是unsigned char control; 会导致结构内部的对齐方式不同,因此用memcpy() 填充它会产生不同的结果。

【讨论】:

    【解决方案3】:

    Memcpy 将字节值从 source 指向的位置直接复制到 destination 指向的内存块。

    源指针和目标指针指向的对象的底层类型与此函数无关;结果是数据的二进制副本。 因此,如果有任何结构填充,那么您将弄乱结果。

    【讨论】:

      【解决方案4】:

      检查sizeof(struct CONTROL) -- 我认为它会是 2 或 4,具体取决于机器。由于您使用的是unsigned 位域(并且unsignedunsigned int 的简写),因此整个结构(struct CONTROL)将至少占用 unsigned int 的大小——即 2 或 4 个字节。

      并且,使用unsigned char control 占用该字段的 1 个字节。因此,control 变量肯定应该存在不匹配。

      尝试如下重写struct control:-

      struct CONTROL
      {
          unsigned char dir : 1;
          unsigned char prm : 1;
          unsigned char fcb : 1;
          unsigned char fcb : 1;
          unsigned char function_code : 4;
      }
      

      【讨论】:

      • 使用无符号字符 x : 1;是非法的
      • @user1244472:不,不是。阅读位域。
      • @user1244472:在 gcc 上,它正在编译。你用的是哪个编译器?
      • crossWorks,我试过 unsigned char a : 1;刚才在 AVR studio 中经历了同样的事情,它在那里编译...现在在 AVR studio 中编写相同的代码,看看它是否在那里工作..
      • 我试着搜索了一下。请参阅此链接:-http://stackoverflow.com/questions/3971085/how-does-a-bit-field-work-with-character-types。从讨论中,似乎处理 char 位字段在某种程度上是实现定义的。
      【解决方案5】:

      干净的方法是使用联合,例如 in.:

      struct HEADER
      {
          unsigned int preamble;
          unsigned char length;
          union {
            unsigned char all;
            struct CONTROL control;
            } uni;
          unsigned int destination;
          unsigned int source;
          unsigned int crc;
      };
      

      结构的用户然后可以选择他想要访问事物的方式。

      struct HEADER thing = {... };
      
      if (thing.uni.control.dir) { ...}
      

      #if ( !FULL_MOON ) /* Update: stacking of bits within a word appears to depend on the phase of the moon */
      if (thing.uni.all & 1) { ... }
      #else
      if (thing.uni.all & 0x80) { ... }
      #endif
      

      注意:此构造解决字节顺序问题,需要隐式转换。

      注意 2:您还必须检查编译器的位字节序。


      还请注意,位域不是很有用,特别是如果数据通过网络传输,并且代码预计在不同的平台上运行,具有不同的对齐方式和/或字节序。普通的unsigned charuint8_t 加上一些位掩码会产生更清晰的代码。例如,检查 BSD 或 linux 内核中的 IP 堆栈。

      【讨论】:

      • 单独的联合是行不通的,因为allcontrol - 成员的大小不同。
      • 你能详细解释一下如何使用它吗?
      • 结构体Control是8位
      • @user1244472 struct Control 大于 8 位,因为你在里面使用了unsigned 类型。
      • 大小不重要,联合成员位于相同的偏移量。如果您想将事物限制为严格的 8 位空间,则必须指示编译器不要在联合内填充 struct CONTROL。 (以及由此产生的联合)
      猜你喜欢
      • 2020-04-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-24
      相关资源
      最近更新 更多