正确处理字节对齐问题 - 通过UDP在16位嵌入式系统和32位桌面之间

Correctly Deal With Byte Alignment Issues -- Between 16 Bit Embeded System and 32 Bit Desktop via UDP

本文关键字:系统 嵌入式 32位 之间 桌面 16位 对齐 字节 问题 UDP 通过      更新时间:2023-10-16

我正在使用的应用程序从嵌入式系统中接收C样式结构,该系统的代码被生成以定位16位处理器。用嵌入式系统说话的应用程序是使用32位GCC编译器或32位MSVC C 编译器构建的。应用程序和嵌入式系统之间的通信是通过以太网或调制解调器上的UDP数据包进行的。

UDP数据包中的有效载荷由各种不同的C样式结构组成。在应用端,C 样式reinterpret_cast能够将无符号字节数组置于适当的结构中。

但是,当结构包含枚举值时,我会遇到reinterpret_cast的问题。16位WATCOM编译器将将枚举值视为UINT8_T类型。但是,在应用侧,枚举值将其视为32位值。当我收到一个带有枚举值的数据包时,数据会被弄乱,因为应用侧的结构大小较大,嵌入式侧的结构很大。

到目前为止,解决此问题的解决方案是将应用程序侧的结构内的枚举类型更改为UINT8_T。但是,这不是一个最佳解决方案,因为我们不能再将成员用作枚举类型。

我正在寻找的是一种解决方案,它可以使我能够使用简单的铸造操作,而不必篡改应用程序侧源的结构定义。通过这样做,我可以按照我的应用程序的上层使用结构。

如前所述,正确处理该问题是正确的序列化和挑战。

,但这并不意味着我们不能尝试一些黑客。

选项1:如果您特定的编译器支持包装枚举(在我的情况下,Windows中的GCC 4.7),这可能有效:

typedef enum { VALUE_1 = 1, VALUE_2, VALUE_3 }__attribute__ ((__packed__)) TheRealEnum;

选项2:

如果您的特定编译器支持<4个字节,您可以使用 hackedenum 类,该类使用操作员重载进行转换(请注意gcc 属性您可能不想要它):

class HackedEnum
{
private:
    uint8_t evalue;
public:
    void operator=(const TheRealEnum v) { evalue = v; };
    operator TheRealEnum() { return (TheRealEnum)evalue; };
}__attribute__((packed));

您将在 hackedenum 的结构中替换,但您仍然继续将其用作therealenum。

一个完整的示例可以看到它有效:

#include <iostream>
#include <stddef.h>
using namespace std;
#pragma pack(push, 1)
typedef enum { VALUE_1 = 1, VALUE_2, VALUE_3 } TheRealEnum;
typedef struct
{
    uint16_t v1;
    uint8_t enumValue;
    uint16_t v2;
}__attribute__((packed)) ShortStruct;
typedef struct
{
    uint16_t v1;
    TheRealEnum enumValue;
    uint16_t v2;
}__attribute__((packed)) LongStruct;
class HackedEnum
{
private:
    uint8_t evalue;
public:
    void operator=(const TheRealEnum v) { evalue = v; };
    operator TheRealEnum() { return (TheRealEnum)evalue; };
}__attribute__((packed));
typedef struct
{
    uint16_t v1;
    HackedEnum enumValue;
    uint16_t v2;
}__attribute__((packed)) HackedStruct;
#pragma pop()
int main(int argc, char **argv)
{
    cout << "Sizes: " << endl
         << "TheRealEnum: " << sizeof(TheRealEnum) << endl
         << "ShortStruct: " << sizeof(ShortStruct) << endl
         << "LongStruct: " << sizeof(LongStruct) << endl
         << "HackedStruct: " << sizeof(HackedStruct) << endl;
    ShortStruct ss;
    cout << "address of ss: " << &ss <<  " size " << sizeof(ss) <<endl
         << "address of ss.v1: " << (void*)&ss.v1 << endl
         << "address of ss.ev: " << (void*)&ss.enumValue << endl
         << "address of ss.v2: " << (void*)&ss.v2 << endl;
    LongStruct ls;
    cout << "address of ls: " << &ls <<  " size " << sizeof(ls) <<endl
         << "address of ls.v1: " << (void*)&ls.v1 << endl
         << "address of ls.ev: " << (void*)&ls.enumValue << endl
         << "address of ls.v2: " << (void*)&ls.v2 << endl;
    HackedStruct hs;
    cout << "address of hs: " << &hs <<  " size " << sizeof(hs) <<endl
         << "address of hs.v1: " << (void*)&hs.v1 << endl
         << "address of hs.ev: " << (void*)&hs.enumValue << endl
         << "address of hs.v2: " << (void*)&hs.v2 << endl;

    uint8_t buffer[512] = {0};
    ShortStruct * short_ptr = (ShortStruct*)buffer;
    LongStruct * long_ptr = (LongStruct*)buffer;
    HackedStruct * hacked_ptr = (HackedStruct*)buffer;
    short_ptr->v1 = 1;
    short_ptr->enumValue = VALUE_2;
    short_ptr->v2 = 3;
    cout << "Values of short: " << endl
            << "v1 = " << short_ptr->v1 << endl
            << "ev = " << (int)short_ptr->enumValue << endl
            << "v2 = " << short_ptr->v2 << endl;
    cout << "Values of long: " << endl
            << "v1 = " << long_ptr->v1 << endl
            << "ev = " << long_ptr->enumValue << endl
            << "v2 = " << long_ptr->v2 << endl;
    cout << "Values of hacked: " << endl
            << "v1 = " << hacked_ptr->v1 << endl
            << "ev = " << hacked_ptr->enumValue << endl
            << "v2 = " << hacked_ptr->v2 << endl;

    HackedStruct hs1, hs2;
    // hs1.enumValue = 1; // error, the value is not the wanted enum
    hs1.enumValue = VALUE_1;
    int a = hs1.enumValue;
    TheRealEnum b = hs1.enumValue;
    hs2.enumValue = hs1.enumValue;
    return 0;
}

我特定系统上的输出是:

Sizes:
TheRealEnum: 4
ShortStruct: 5
LongStruct: 8
HackedStruct: 5
address of ss: 0x22ff17 size 5
address of ss.v1: 0x22ff17
address of ss.ev: 0x22ff19
address of ss.v2: 0x22ff1a
address of ls: 0x22ff0f size 8
address of ls.v1: 0x22ff0f
address of ls.ev: 0x22ff11
address of ls.v2: 0x22ff15
address of hs: 0x22ff0a size 5
address of hs.v1: 0x22ff0a
address of hs.ev: 0x22ff0c
address of hs.v2: 0x22ff0d
Values of short:
v1 = 1
ev = 2
v2 = 3
Values of long:
v1 = 1
ev = 770
v2 = 0
Values of hacked:
v1 = 1
ev = 2
v2 = 3

在应用程序端A C 样式Reinterpret_cast能够将无符号字节数组施加到适当的结构中。

在不同的实现之间,不需要结构的布局相同。以这种方式使用reinterpret_cast是不合适的。

16位WATCOM编译器将将枚举值视为UINT8_T类型。但是,在应用方面,枚举值被视为32位值。

实施方式选择了枚举的基本类型,并以实现方式选择。

这只是实现之间可能导致retinterpret_cast问题的许多潜在差异之一。如果您不小心,在接收到的缓冲区中的数据不适当地对齐类型(例如,一个需要四个字节对齐的整数最终会删除一个字节),这也可能会导致崩溃或差,也存在实际的对齐问题。表现。平台之间的填充可能有所不同,基本类型可能具有不同的尺寸,Endianess可能会有所不同,等等。

我正在寻找的是一种解决方案,它可以使我能够使用简单的铸造操作,而不必篡改应用程序侧源的结构定义。通过这样做,我可以按照我的应用程序的上层使用结构。

c 11引入了一种新的枚举语法,该语法允许您指定基础类型。或者,您可以用积分类型以及带有手动声明值的一堆预定义常数替换枚举。这仅解决您要问的问题,而不是您遇到的其他问题。

您真正应该做的是适当的序列化和应对序列化。

将您的枚举类型放在带有32位数字的联盟中:

union
{
  Enumerated val;
  uint32_t valAsUint32;
};

这将使嵌入式一侧扩展到32位。只要两个平台都是小范围,并且结构最初为零。但是,这将改变电线格式。

如果通过"简单铸造操作",您的意思是在源代码中表达的东西,而不是必然是零副本的东西,那么您可以编写两个版本的结构 - 一个版本 - 一个枚举,一个带有uint8_ts的枚举,另一个来自另一个的构造函数,将其复制为元素以重新包装。然后,您可以在代码的其余部分中使用普通的类型铸造。由于数据大小在根本上是不同的(除非您使用另一个答案中提到的C 11个功能),因此您不能在不复制内容重新包装的情况下执行此操作。

但是,如果您不介意应用程序端的结构定义的一些小更改,则有几个选项不涉及处理Bare uint8_t值。您可以使用AARONPS的答案,该类别是UINT8_T的大小(假设您的编译器可能是可能的),并隐式地转换为枚举。或者,您可以将值存储为UINT8_TS,并为您的枚举值编写一些访问者方法,这些方法将UINT8_T数据输入结构中并在返回枚举之前将其转换为枚举。