Переменная в случае переключателя: UB или ошибка компилятора? - PullRequest
2 голосов
/ 08 апреля 2019

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

Я разрабатывал некоторый код для интерпретации сообщений, отправляемых на Arduino от внешнего компонента через последовательное соединение.Вот упрощенная версия функции-члена, с которой я начал.[Команды Serial.println являются Arduino-эквивалентом отладки printf.]

void decodeMessage() {
  switch (getType()) {
    case 0x3A:
      Serial.println("foo message");
      break;
    case 0x3B:
      Serial.println("bar message");
      break;
    case 0x3C:
      Serial.println("zerz message");
      break;
    ... // and so on for 0x3D through 0x40
    case 0x41:
      Serial.println("ack message");
      break;
    default:
      Serial.println("unknown message type");
      break;
  }
}

Это работало нормально для всех типов сообщений.Затем я изменил регистр для 0x3B, чтобы также проверить некоторые биты в параметре сообщения:

    case 0x3B:
      Serial.println("bar message");
      const auto mask = getParam();
      if (mask & 0x01) Serial.println("bit 0 set");
      if (mask & 0x02) Serial.println("bit 1 set");
      break;

При замене этого кода на исходный регистр 0x3B все работало , кроме для последнего сообщениятип (0x41, "ack").Как будто тело этого дела пропало.Случай по умолчанию продолжал работать, как и с 0x3A по 0x40.

После многих попыток выяснить причину проблемы я понял, что ввел переменную const (mask) всередина переключателя, не рассматривая его в этом конкретном случае.Когда я добавил фигурные скобки, это снова сработало для всех случаев:

    case 0x3B: {
      Serial.println("bar message");
      const auto mask = getParam();
      if (mask & 0x01) Serial.println("bit 0 set");
      if (mask & 0x02) Serial.println("bit 1 set");
      break;
    }  // braces to limit scope of `mask`

Вопросы:

  • Вызывает ли неработающая версия неопределенное поведение илиэто ошибка компилятора?Если UB, какой раздел спецификации мне следует перечитать?

  • Другие используемые мной компиляторы (например, VC ++) выдают предупреждение, когда вы вводите переменную внутри регистра переключателябез ограничения его объема.Есть ли возможность получить подобное предупреждение от gcc (который является компилятором, который используется в Arduino IDE)?

1 Ответ

7 голосов
/ 08 апреля 2019

Я считаю, что этот код должен был быть неверно сформирован на основе [stmt.dcl] / 3 :

Возможно передать в блок, но не вспособ обойти объявления с инициализацией (в том числе в условиях и инструкциях init). Программа, которая переходит от точки, в которой переменная с автоматическим хранением находится вне области действия, к точке, в которой она находится в области видимости, является некорректно сформированной , если переменная не имеет бессодержательной инициализации ([dcl.init]).

Ударная шахта.Ваша переменная mask не имеет незаполненной инициализации .По крайней мере, насколько я понимаю, «плохо сформированный» неявно требует диагностики, то есть компилятор, соответствующий стандартам, должен выдавать сообщение об ошибке.Так что никакого неопределенного поведения, это просто не должно было бы скомпилироваться.

Таким образом, я бы сказал, что отсутствие диагностики здесь определенно следует рассматривать как ошибку компилятора.Обратите внимание, однако, что ни одна из версий GCC, которую можно примерить на Godbolt, не принимает этот код (и они уходят довольно далеко назад).Казалось бы, в Arduino IDE должна использоваться безнадежно устаревшая / неработающая версия GCC, если она действительно скомпилировала этот код без колебаний ...

пример правильных компиляторов, жалующихся на этот

Чтобы устранить проблему, просто оберните вашу переменную в область видимости блока так, чтобы не было никакого возможного потока управления, который мог бы войти в область, в которой переменная объявлена, без передачи ее объявления, как вы уже обнаружили сами.Например, превратить это

void f(int x)
{
    switch (x)
    {
    case 1:
        const int y = 42;  // error
        break;

    case 2:
        break;
    }
}

в

void f(int x)
{
    switch (x)
    {
    case 1:
        {
            const int y = 42;  // OK
            break;
        }

    case 2:
        break;
    }
}
...