带有枚举的结构在C和C++中有所不同,为什么?

Bul*_*kin 13 c c++ struct arduino stm32

任务是通过I2C将数据从Arduino发送到STM32.

所以我使用C++在Arduino中定义了Struct和Enums:

enum PhaseCommands {
    PHASE_COMMAND_TIMESYNC  = 0x01,
    PHASE_COMMAND_SETPOWER  = 0x02,
    PHASE_COMMAND_CALIBRATE = 0x03
};

enum PhaseTargets {
    PHASE_CONTROLLER = 0x01,
    // RESERVED = 0x02,
    PHASE_LOAD1 = 0x03,
    PHASE_LOAD2 = 0x04
};

struct saatProtoExec {
    PhaseTargets   target;
    PhaseCommands  commandName;
    uint32_t       commandBody;
} phaseCommand;

uint8_t phaseCommandBufferSize = sizeof(phaseCommand);

phaseCommand.target = PHASE_LOAD1;
phaseCommand.commandName = PHASE_COMMAND_SETPOWER;
phaseCommand.commandBody = (uint32_t)50;
Run Code Online (Sandbox Code Playgroud)

另一方面,我使用C得到了相同的定义:

typedef enum {
    COMMAND_TIMESYNC  = 0x01,
    COMMAND_SETPOWER  = 0x02,
    COMMAND_CALIBRATE = 0x03
} MasterCommands;

typedef enum {
    CONTROLLER = 0x01,
    // RESERVED = 0x02,
    LOAD1 = 0x03,
    LOAD2 = 0x04
} Targets;

struct saatProtoExec {
    Targets         target;
    MasterCommands  commandName;
    uint32_t        commandBody;
} execCommand;

uint8_t execBufferSize = sizeof(execCommand);

execCommand.target = LOAD1;
execCommand.commandName = COMMAND_SETPOWER;
execCommand.commandBody = 50;
Run Code Online (Sandbox Code Playgroud)

然后我逐字节比较这个Structs:

=====================
BYTE    | C++   |  C
=====================
Byte 0 -> 0x3  -> 0x3
Byte 1 -> 0x0  -> 0x2
Byte 2 -> 0x2  -> 0x0
Byte 3 -> 0x0  -> 0x0
Byte 4 -> 0x32 -> 0x32
Byte 5 -> 0x0  -> 0x0
Byte 6 -> 0x0  -> 0x0
Byte 7 -> 0x0  -> 0x0
Run Code Online (Sandbox Code Playgroud)

那么为什么字节1和2不同呢?

unw*_*ind 24

这是一个非常糟糕的主意.

你永远不应该依赖于C的两个实现之间的结构的二进制表示,更不用说从C到C++了!

您应该执行一些正确的序列化/反序列化代码,以控制结构外部表示的字节级别.

也就是说,这可能是由于填充.你最终通过外部链接发送填充(这只是编译器添加的东西,以保持其主机CPU满意)是另一个迹象表明这种方法有多么破碎.


rod*_*igo 9

在C版本中很明显sizeof(Targets) == 1.看起来结构的第二个字段是2字节对齐的,所以你有一个填充字节,内容未定义.

现在,在C++中,sizeof(PhaseTargets)可能是12.如果它1(可能)一切都很好并且你有相同的填充空间,恰好有不同的垃圾值.如果是2......好吧,你的枚举值会有误!

初始化结构的简单方法是在变量的定义中.如果还没有值,只需添加一个0,所有结构将初始化为0.

struct saatProtoExec execCommand = {0};
Run Code Online (Sandbox Code Playgroud)

如果无法完成,则可以memset()在使用前将其归零.

一个可移植的替代方法是将结构的字段声明为适当大小的整数,并将这些enum类型用作常量集合.

struct saatProtoExec {
    uint8_t         target;
    uint8_t         commandName;
    uint8_t         padding[2];
    uint32_t        commandBody;
} execCommand;
Run Code Online (Sandbox Code Playgroud)

  • 此替代方案仅适用于具有相同endianess和32位aligment或less(这似乎是此特定问题的情况)的体系结构.但是,使用[unwind的答案](http://stackoverflow.com/a/40465574/3951057)中指出的序列化是实际上可移植的解决方案,并且还可以提高带宽效率. (2认同)