【问题标题】:Problem when copying byte array into c structure将字节数组复制到c结构时出现问题
【发布时间】:2019-09-22 02:27:22
【问题描述】:

我知道这可能以前已经回答过了,但我仍然无法解决我认为是字节顺序问题的解决方案。我在下面构建了一个快速示例来演示我的测试代码。

https://onlinegdb.com/SJtEatMvS

在这个例子中,我有一个简单的字节数组。实际上,这个字节数组是通过 CAN 收集的一个较大的数据集,但为了这个问题,我使用了一个较小的硬编码数组。

目标

我在 c 中的目标是将字节数组复制到一个结构中,同时保留数组所在的顺序(如果有意义的话)。例如

数据集包含:

{0x12, 0x34, 0x56, 0x78, 0x0A, 0x06, 0x77}

而结构定义为

typedef struct {
    uint8_t  test0;
    uint16_t test1;
    uint32_t test2;
} Foo_t;

我希望将 0x12 复制到 test0,将 {0x3456} 复制到 test1,并将 {0x780A0677} 复制到 test2。如上所述,我使用了一个小数组进行测试,但实际数组很大,因此手动分配结构成员对我来说不是一个选项。

我知道memcpy 不是问题,因为它不关心字节顺序,实际问题是我对数据应该如何对齐的假设。在主机方面,这是在 Windows 系统上运行的,我相信它是 little endian。

【问题讨论】:

  • 如果你想使用memcpy,你有两个问题:字节序是一个,因为你怀疑你必须在Windows上为多字节值交换字节顺序。另一个问题是对齐/填充:默认情况下,您上面的结构将具有填充字节以在自然边界上对齐每个字段(可被其大小整除的偏移量)。
  • 翻转结构成员的顺序,不要让编译器填充,然后从最后一个字节复制到开头。这是一种选择。
  • 由于已经提到的问题,您最好诚实地编写代码来反序列化字节数组数据并一一分配给结构字段,并根据需要注意字节顺序。

标签: c endianness


【解决方案1】:

由于不完全理解您的问题,我删除了我的原始答案。在阅读以下文章后,我现在明白了:Writing endian-independent code in C

首先是对齐问题:

如 500 所述 - 内部服务器错误

您在处理数据时会遇到问题,因为您的结构将包含填充。在您的示例中,将向结构添加 1 个字节。

这是一个从 VS 获得的 32 位 C 实现的内存布局示例。

size = 8
Address of test0        = 5504200
Padding added here at address 5504201
Address of test1        = 5504202
Address of test2        = 5504204

要指定编译器应该使用的对齐规则,请使用预处理器指令pack

// Aligns on byte boundaries, then restore alignment value to system defaults
#pragma pack ( 1 )
#pragma pack ()

// Aligns on byte boundaries, restores previously assigned alignment value.
#pragma pack ( push, 1 )
#pragma pack (pop)

使用您的示例,结构定义将如下所示:

#pragma pack ( 1 )
typedef struct {
    unsigned char  test0;
    unsigned short test1;
    unsigned int   test2;
} Foo_t;
#pragma pack ()

Foo_t s2;

printf("\nsize = %d\n", sizeof(Foo_t));

printf("   Address of test0        = %u\n", &s2.test0);
printf("   Address of test1        = %u\n", &s2.test1);
printf("   Address of test2        = %u\n", &s2.test2);

结果:

size = 7
Address of test0        = 10287904
Address of test1        = 10287905
Address of test2        = 10287907

第二个字节序问题:

这里的问题是整数是如何存储在 32 位 x86 机器上的内存中的。在 x86 机器上,它们以 little endian 顺序存储。

例如,将包含字节 x34 和 x56 的 2 字节数组复制到一个短整数中,将存储为 x56(低位字节)x34(下一个字节)。这不是你想要的。

要解决此问题,您需要按照其他建议切换字节。我对此的看法是创建一个可以就地进行字节交换的函数。

例子:

int main()
{

#pragma pack ( 1 )
typedef struct {
    unsigned char  test0;
    unsigned short test1;
    unsigned int   test2;
} Foo_t;
#pragma pack ()

    unsigned char tempBuf[7] = { 0x12, 0x34, 0x56, 0x78, 0x0A, 0x06, 0x77 };

    Foo_t foo;

    memcpy(&foo, &tempBuf[0], 7);

    //foo.test0 = netToHost (&foo,0,1);  // not needed
    foo.test1 = reverseByteOrder(&foo, 1, 2);
    foo.test2 = reverseByteOrder(&foo, 3, 4);

    printf("\n After memcpy We have %02X %04X %08X\n", foo.test0, foo.test1, foo.test2);
}


int reverseByteOrder(char array[], int startIndex, int size)
{
    int intNumber =0;

    for (int i = 0; i < size; i++)
        intNumber = (intNumber << 8) | array[startIndex + i];

    return intNumber;
}

输出是:

After memcpy We have 12 3456 780A0677

【讨论】:

  • 最好使用#pragma pack(push,1) / #pragma(pop)(如果在您的编译器上可用)来保留以前可能设置的包值。
  • 感谢 Matthieu 的编辑和额外的包信息。我已将您的 cmets 包含在答案中。
  • 另外,请注意,如果需要,您有函数 ntoh*() 可以为您执行字节重新排序。
  • 谢谢你们俩。这个解释非常有帮助!
【解决方案2】:

字节序与 CPU 相关联,而不是操作系统。但由于 Windows 仅在 x86 上运行且 x86 是 little-endian,因此 Windows 是 little-endian(嗯,它们似乎也有 ARM 版本,但大多数 ARM 也是 little-endian)。

由于您的数据是大端的,因此您必须将其转换为您的处理器端。但 big-endian 也是标准的网络字节顺序,因此您可以依靠 ntoh*() 函数为您完成此操作。不幸的是,这意味着您必须为每个字段手动完成...

如果你的 CPU 是大字节序的,你可以用 #pragma pack(1)memcpy() 打包你的结构体(或转换一个指针)。

【讨论】:

    【解决方案3】:
    uint8_t data[] = {0x12, 0x34, 0x56, 0x78, 0x0A, 0x06, 0x77};
    typedef struct {
        uint8_t  test0;
        uint16_t test1;
        uint32_t test2;
    } Foo_t;
    
    Foo_t fs;
    fs.test0 = data[0];
    fs.test1 = data[1]<<8 + data[2];
    fs.test2 = data[3]<<24 + data[4]<<16 + data[5]<<8 + data[6];
    

    如果你的处理器是大端,你可以作弊一点..

    fs.test0 = data[0];
    fs.test1 = *(uint16_t*)&data[1];
    fs.text2 = *(uint32_t*)&data[3];
    

    如果您的数组的字节顺序与您的处理器匹配,并且您的结构中有许多变量,您可以使用 __packed 属性和 memcpy()。

    typedef __packed struct {
        uint8_t  test0;
        uint16_t test1;
        uint32_t test2;
    } Foo_t;
    
    Foo_t fs;
    memcpy(&fs, data, sizeof(fs));
    

    【讨论】:

      猜你喜欢
      • 2021-06-19
      • 2021-09-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-30
      • 1970-01-01
      • 2017-02-12
      • 2016-12-09
      相关资源
      最近更新 更多