MSVC 1-битный тип перечисления равен -1, и проверка на равенство не пройдена - PullRequest
0 голосов
/ 28 мая 2019

Я определил битовое поле типов перечисления, чтобы соответствовать набору перемычек во встроенной системе.Я пытаюсь написать тестовый жгут в MSVC для кода, но сравнивать то, что должно быть равными значениями, не удается.

Определение выглядит так:

typedef enum { SERIAL_TEST_MODE, PARALLEL_TEST_MODE } TEST_MODE_e;
typedef union {
    struct {
        ACTUATOR_TYPE_e     ActuatorType        : 1;    // 1
        NORMAL_POSITION_e   Damper1NormalPos    : 1;    // 2
        NORMAL_POSITION_e   Damper2NormalPos    : 1;    // 3
        bool                EnableDamper2       : 1;    // 4
        NETWORK_MODE_e      NetworkMode         : 1;    // 5
        FIRE_ZONE_TYPE_e    FireZoneType        : 1;    // 6
        bool                PeriodicTestEn      : 1;    // 7
        TEST_TIME_e         TestTime            : 3;    // 8-10
        TEST_MODE_e         TestMode            : 1;    // 11
        bool                TestAHUEn           : 1;    // 12
        bool                TestDelayEn         : 1;    // 13
        INPUT_1_MODE_e      Input1Mode          : 1;    // 14
        bool                NightModeAHUEn      : 1;    // 15
        ETR_ACT_MODE_e      ETRActMode          : 1;    // 16
        bool                DSTEnable           : 1;    // 17
    } Bits;
    uint32_t Word;
} DIP_SWITCHES_t;

Позже следующее сравнение не удается:

config.jumpers.Bits.TestMode = PARALLEL_TEST_MODE;
if (config.jumpers.Bits.TestMode == PARALLEL_TEST_MODE)
    ...

Я проверил TestMode bool в отладчике, и это выглядит странно.Значение TestMode равно -1.

screenshot of debugger showing value of TestMode

Это похоже на то, как если бы MSVC рассматривал значение как число дополнения до 2, но ширину 1 бит, поэтому 0b1 является десятичным -1.Перечисление устанавливает PARALLEL_TEST_MODE в 1, поэтому два значения не совпадают.

Сравнение отлично работает на встроенной стороне с использованием LLVM или GCC.

Какое поведение является правильным?Я предполагаю, что GCC и LLVM лучше поддерживают стандарты C, чем MSVC в таких областях, как битовые поля.Что еще более важно, можно ли обойти эту разницу, не внося существенных изменений во встроенный код?

Ответы [ 4 ]

2 голосов
/ 29 мая 2019

Рассматривая это подробно, у вас есть следующие проблемы:

  • Нет никаких гарантий, что ActuatorType : 1 является MSB или LSB.Как правило, нет никаких гарантий размещения битовых полей в памяти.
  • Как упоминалось в других ответах, переменные перечисления (в отличие от констант перечисления) имеют размер, определенный реализацией.Это означает, что вы не можете знать их размер, переносимо.Кроме того, если размер не совпадает с int или _Bool, компилятору вообще не требуется его поддержка.
  • Перечисления чаще всего представляют собой целочисленный тип со знаком.И когда вы создаете битовое поле размера 1 со знаком, никто, включая стандарт, не знает, что это значит.Это знаковый бит, который вы намереваетесь хранить там, или это данные?
  • Размер, который стандарт C называет «единицей хранения» внутри битового поля, не определен.Как правило, это на основе выравнивания.Стандарт C действительно гарантирует, что если у вас есть несколько битовых полей одного типа, идущих друг за другом, они должны быть объединены в одну и ту же единицу хранения (если есть место).Для разных типов таких гарантий нет.

    Довольно часто при переходе от одного типа, например NORMAL_POSITION_e, к другому типу bool компилятор размещает их в разных единицах хранения.На практике это означает, что существует высокий риск вставки битов заполнения, когда это происходит.Многие основные компиляторы на самом деле ведут себя именно так.

  • Кроме того, структура или объединение могут содержать байты заполнения в любом месте.

  • ВКроме того, существует проблема с порядком байтов.

Вывод: битовые поля нельзя использовать в программах, которые нуждаются в переносимости.Они не могут использоваться для отображения памяти.

Кроме того, вам действительно не нужны все эти уровни абстракции - это простой DIP-переключатель, а не космический челнок!:)


Решение:

Я бы настоятельно рекомендовал отказаться от всего этого в пользу простого uint32_t.Вы можете замаскировать отдельные биты с помощью простых целочисленных констант:

#define DIP_ACTUATOR_TYPE (1u << 31)
#define DIP_DAMPER1_POS   (1u << 30)
...

uint32_t dipswitch = ...;
bool actuator_active = dipswitch & DIP_ACTUATOR_TYPE; // read
dipswitch |= DIP_DAMPER1_POS;     // write

Это очень портативный, четко определенный, стандартизированный, совместимый с MISRA-C - вы даже можете переносить его между различными архитектурами с порядковыми номерами.Решает все вышеперечисленные проблемы.

1 голос
/ 28 мая 2019

Придуманное мной простое исправление, действительное только для MSVC и GCC / LLVM:

#ifdef  _WIN32
#define JOFF        0
#define JON         -1
#else
#define JOFF        0
#define JON         1
#endif

typedef enum { SERIAL_TEST_MODE = JOFF, PARALLEL_TEST_MODE = JON } TEST_MODE_e;
0 голосов
/ 28 мая 2019

Я бы использовал следующий подход.

typedef enum { SERIAL_TEST_MODE = 0, PARALLEL_TEST_MODE = 1 } TEST_MODE_e;

Затем установите значение и проверьте его следующим образом.

config.jumpers.Bits.TestMode = PARALLEL_TEST_MODE;
if (config.jumpers.Bits.TestMode & PARALLEL_TEST_MODE)
    ...

Значение -1 будет включать младший значащий бит, а значение 0 будет иметь младший значащий бит выключенным.

И это должно переноситься на несколько компиляторов.

0 голосов
/ 28 мая 2019

Точный тип, используемый для представления enum, определяется реализацией.Так что, скорее всего, происходит то, что MSVC использует char для этого конкретного enum, который подписан.Поэтому объявление 1-битного битового поля этого типа означает, что вы получаете 0 и -1 для значений.

Вместо того, чтобы объявлять битовое поле как тип enum, объявите их как unsigned int или * 1008.* Значения правильно представлены.

...