Переменная в случае переключения: ошибка UB или компилятора?

#c #gcc #arduino #switch-statement #language-lawyer

#c #gcc #arduino #switch-оператор #язык-юрист

Вопрос:

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

Я разрабатывал некоторый код для интерпретации сообщений, отправляемых на 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 amp; 0x01) Serial.println("bit 0 set");
      if (mask amp; 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 amp; 0x01) Serial.println("bit 0 set");
      if (mask amp; 0x02) Serial.println("bit 1 set");
      break;
    }  // braces to limit scope of `mask`
  

Вопросы:

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

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

Ответ №1:

Я считаю, что этот код должен был быть неправильно сформирован на основе [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;
    }
}
  

Комментарии:

1. Вы должны добавить возможное исправление

2. Хорошая ссылка на стандарты. Я думаю, что Arduino использует пользовательскую версию gcc под названием avr-gcc, но я не знаю подробностей об этом. Кажется правдоподобным, что это был форк из более старого gcc. Я уже понимаю исправление, как я показал в вопросе. Я просто пытаюсь понять, как это привело к особенно странному поведению без предупреждения.

3. @AdrianMcCarthy Я изначально не добавлял исправление, потому что мне не показалось, что вы просили его (уже обнаружив решение самостоятельно). Я добавил это сейчас в любом случае на случай, если кто-то в будущем может счесть это полезным. Что касается GCC, может быть, вы компилируете свой код как C, а не C ? Похоже, здесь есть потенциально связанная ошибка: gcc.gnu.org/bugzilla/show_bug.cgi?id=87038

4. Ошибка, безусловно, находится в пределах допустимого. Но код определенно скомпилирован как C , поскольку рассматриваемая функция является функцией-членом.