简体   繁体   English

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

[英]Structs with enums are different in C and C++, why?

The task is to send data by I2C from Arduino to STM32. 任务是通过I2C将数据从Arduino发送到STM32。

So I got Struct and Enums defined in Arduino using C++: 所以我用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;

On the other side I got the same defined using C: 另一方面,我使用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;

And then I compare this Structs byte-by-byte: 然后我逐字节比较此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

So why bytes 1 and 2 are different? 那么,为什么字节1和2不同?

This is a really bad idea. 这真是个坏主意。

You should never rely on the binary representation of structures being the same beween two implementations of C, not to mention going from C to C++! 您绝不应该依赖在C的两个实现之间相同的结构的二进制表示,更不用说从C到C ++了!

You should do some proper serialization/deserialization code, to take control at the byte level of the structure's external representation. 您应该执行一些适当的序列化/反序列化代码,以控制结构外部表示的字节级别。

That said, it could be due to padding. 就是说,这可能是由于填充。 That you end up sending padding (which is just something added by a compiler to keep its host CPU happy) over an external link is another sign of how broken this approach is. 您最终在外部链接上发送填充(这是编译器添加的,目的是使主机CPU保持快乐),这是此方法破损的另一个标志。

In the C version it is clear that sizeof(Targets) == 1 . 在C版本中,很明显sizeof(Targets) == 1 And it looks like the second field of the struct is 2-byte aligned, so you have a padding byte, with undefined contents. 而且看起来该结构的第二个字段是2字节对齐的,因此您有一个填充字节,其中包含未定义的内容。

Now, in C++, sizeof(PhaseTargets) may be 1 or 2 . 现在,在C ++中, sizeof(PhaseTargets)可以为12 If it is 1 (likely) all is well and you have the same padding space, just happened to have different garbage value. 如果它是1 (可能),那么一切都很好,并且您具有相同的填充空间,只是碰巧具有不同的垃圾值。 If it is 2 ... well, you'll have a wrong enum value! 如果它是2 ...那么,您将有错误的枚举值!

The easy way to initialize the struct would be in the definition of the variable. 初始化结构的简单方法是在变量的定义中。 If you don't have the values yet, just add a 0 and all the struct will be 0 initialized. 如果还没有值,只需添加0,所有结构都将初始化为0。

struct saatProtoExec execCommand = {0};

If that cannot be done, you can memset() it to zero before using. 如果无法做到这一点,则可以在使用前将其memset()设置为零。

A portable alternative would be to declare the fields of the struct as integers of the proper size, and use the enum types just as collections of constants. 一种可移植的替代方法是将结构的字段声明为适当大小的整数,并使用enum类型作为常量的集合。

struct saatProtoExec {
    uint8_t         target;
    uint8_t         commandName;
    uint8_t         padding[2];
    uint32_t        commandBody;
} execCommand;

Well, as already said, you should not rely on a structure having a specific format. 好了,正如已经说过的那样,您不应该依赖具有特定格式的结构。 However, it is sometime convenient to use a structure instead of serialization as it can be more efficient if no conversion are required and the representation is compatible and efficient at run-time on a given architecture. 但是,使用结构而不是序列化有时会很方便,因为如果不需要转换并且在给定体系结构上表示形式在运行时兼容且高效,则结构会更高效。

Thus, here are some advices when using structures: 因此,以下是使用结构时的一些建议:

  • Uses appropriate pragmas or attributes to ensure that the layout does not depends on project options. 使用适当的编译指示或属性来确保布局不依赖于项目选项。
  • Add checks to validate that the final size is the expected one. 添加检查以验证最终大小是否是预期的大小。 In C++, you can use static_assert . 在C ++中,可以使用static_assert
  • Also add check that the endianness is the expected one. 还要添加字节序是否为预期字符。
  • Having unit test to confirm the format would also be a good idea. 进行单元测试以确认格式也是一个好主意。

In any case, I would also recommend to avoid using the same struct in your general code and for serialization. 无论如何,我也建议您避免在通用代码中使用相同的struct并进行序列化。 Sometime, it is useful to have additional members that are not serialized or do some adjustments. 有时,让其他未序列化或进行一些调整的成员很有用。

Also another important thing is to plan for the fact that at some point, you might need to add additional fields and convert old data or some bugs might be found and require data to be corrected. 另一个重要的事情是计划以下事实:在某个时候,您可能需要添加其他字段并转换旧数据,或者可能会发现一些错误并需要更正数据。

You might also want to consider what should happen if a new file is opened in an old software. 您可能还需要考虑如果在旧软件中打开新文件会发生什么情况。 In many cases, it would probably be rejected. 在许多情况下,它可能会被拒绝。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM