将字节数组复制到C结构时出现问题

时间:2019-09-20 17:40:35

标签: c endianness

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

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。如上所述,我使用了一个小的数组进行测试,但是实际的数组很大,因此手动分配struct成员对我来说不是一个选择。

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

3 个答案:

答案 0 :(得分:1)

由于未完全理解您的问题,我删除了原始答案。在阅读以下有关Writing endian-independent code in C

的文章后,我现在知道了

首先是对齐问题:

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

您将无法处理数据,因为您的结构将包含填充。在您的示例中,将在结构中添加1个字节。

这是在32位C实现中从VS获取的内存布局示例。

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机器上,它们以小端顺序存储。

例如,将包含字节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

答案 1 :(得分:1)

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

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

如果您的CPU采用大尾数形式,则可以用#pragma pack(1)memcpy()将结构打包(或强制转换指针)。

答案 2 :(得分:0)

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));