Недостаточный анализ потока управления переключателем перечисления в GCC

В следующем коде С++:

typedef enum { a, b, c } Test;

int foo(Test test) {
    switch (test) {
        case a: return 0;
        case b: return 1;
        case c: return 0;
    }
}

при компиляции с -Wall выдается предупреждение о том, что управление достигает конца не-void функции. Почему?


Изменить

В целом не так правильно сказать, что переменная test в этом примере может содержать любое значение.

foo(12354) не компилируется:

> test.cpp:15:14: error: invalid conversion from ‘int’ to ‘Test’
> test.cpp:15:14: error:   initializing argument 1 of ‘int foo(Test)’

потому что 12354 не является допустимым значением test (хотя оно действительно было бы справедливым в простой C, но оно не в С++).

Вы можете явно указать произвольную целочисленную константу в тип перечисления, но разве это не считается Undefined Поведение?

Ответ 1

Проблема в том, что переменная типа Test может иметь любое значение, разрешенное типом, который дает компилятор. Поэтому, если он решил, что это 32-разрядное целое число без знака, любое значение в этом диапазоне разрешено. Итак, если вы вызываете foo(123456), ваш оператор switch не поймает никакого значения и не будет return после вашего switch.

Поместите случай default в свой коммутатор или добавьте код обработки ошибок.

Ответ 2

Хотя существует реальная опасность передачи foo аргумента, который не попадет ни в один из операторов return, предупреждение не зависит от перечисления или от опасности. Вы можете видеть тот же эффект с bool, в заявлении переключателя, который (насколько я могу судить) полностью водонепроницаем.

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

Таким образом, вывод должен остановиться где-то, и (по крайней мере, с gcc) он останавливается с определением, что нет случая по умолчанию, и поэтому управление может оставить switch без нажатия return.

Ответ 3

Нет гарантии, что переменная test будет содержать допустимое перечисление, поэтому на самом деле вы можете достичь конца своей не-void функции, например. если ваш код вызова выглядит следующим образом:

Test test = Test(3);
foo(test);

Ответ 4

Хотя ваш enum имеет только три объявленных состояния, реализация фактически выберет более крупный интегральный тип (обычно int) для хранения значений, которые НЕ ограничены объявленными. Стандарт просто дает некоторые минимальные гарантии, чтобы гарантировать, что он может хотя бы обрабатывать указанные значения и определенные комбинации. Иногда эта свобода имеет важное значение, поскольку значения enum должны быть побитово-ORed вместе для формирования комбинаций. Таким образом, функцию foo можно вызвать, скажем, Test(3), которая не найдет в switch оператора return.