Подтвердить что ты не робот

Структуры с перечислениями различны в C и С++, почему?

Задача - отправить данные с I2C от Arduino до STM32.

Итак, я получил Struct и Enums, определенные в Arduino, используя С++:

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;

С другой стороны, я получил то же определение, что и 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;

И затем я сравниваю этот байт по-строкам:

=====================
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

Итак, почему байты 1 и 2 отличаются?

4b9b3361

Ответ 1

Это действительно плохая идея.

Вы никогда не должны полагаться на двоичное представление структур, являющихся одним и тем же двумя реализациями C, не говоря уже о переходе от C в С++!

Вам нужно сделать правильный код сериализации/десериализации, чтобы взять управление на уровне байта внешнего представления структуры.

Тем не менее, это может быть связано с заполнением. То, что вы в конечном итоге отправляете заполнение (что просто добавлено компилятором, чтобы поддерживать его хост-процессор счастливым) по внешней ссылке, является еще одним признаком того, насколько нарушен этот подход.

Ответ 2

В версии C ясно, что sizeof(Targets) == 1. И похоже, что второе поле структуры выровнено по 2 байт, поэтому у вас есть байт заполнения, с содержимым undefined.

Теперь, в С++, sizeof(PhaseTargets) может быть 1 или 2. Если это 1 (вероятно), все хорошо, и у вас есть одно и то же пространство для заполнения, просто произошло различное значение для мусора. Если это 2... ну, у вас будет неправильное значение перечисления!

Простым способом инициализации структуры будет определение переменной. Если у вас еще нет значений, просто добавьте 0, и вся структура будет инициализирована 0.

struct saatProtoExec execCommand = {0};

Если это невозможно сделать, вы можете memset() до нуля до использования.

Переносимой альтернативой было бы объявить поля структуры целыми числами правильного размера и использовать типы enum так же, как коллекции констант.

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

Ответ 3

Ну, как уже было сказано, вы не должны полагаться на структуру, имеющую определенный формат. Однако иногда удобно использовать структуру вместо сериализации, поскольку она может быть более эффективной, если не требуется преобразование, и представление совместимо и эффективно во время выполнения в заданной архитектуре.

Таким образом, вот несколько советов при использовании структур:

  • Использует соответствующие прагмы или атрибуты, чтобы гарантировать, что макет не зависит от параметров проекта.
  • Добавьте проверки для проверки того, что конечный размер является ожидаемым. В С++ вы можете использовать static_assert.
  • Также добавьте проверку, что конечный результат является ожидаемым.
  • Наличие unit test для подтверждения формата также будет хорошей идеей.

В любом случае, я бы также рекомендовал не использовать один и тот же struct в вашем общем коде и для сериализации. Иногда полезно иметь дополнительные члены, которые не сериализованы или не выполняются какие-либо корректировки.

Еще одна важная вещь - планировать тот факт, что в какой-то момент вам может потребоваться добавить дополнительные поля и преобразовать старые данные или найти некоторые ошибки и потребовать исправления данных.

Вы также можете подумать о том, что должно произойти, если новый файл открывается в старом программном обеспечении. Во многих случаях это, вероятно, будет отклонено.