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

Защитный код после переключения на перечисление никогда не достигается

Я просто попал в запутанную проблему при попытке скомпилировать некоторый код с помощью g++ 4.4.3.

Код ниже компилируется отлично, но вместо того, чтобы нажимать ожидаемое утверждение, когда я передаю "недопустимое" значение enum, функция просто возвращает 1. То, что я нахожу еще более странным, - это то, что когда я раскомментирую строки, относящиеся к значению перечисления E3, все начинает работать, как ожидалось.

Тот факт, что в блоке коммутатора нет записи по умолчанию, по дизайну. Мы компилируем с параметром -Wall для получения предупреждений о необработанных значениях перечисления.

enum MyEnum
{
    E1,
    E2, 
    //E3
};

int doSomethingWithEnum(MyEnum myEnum)
{
    switch (myEnum)
    {
        case E1: return 1;
        case E2: return 2;
        //case E3: return 3;
    }

    assert(!"Should never get here");
    return -1;
}

int main(int argc, char **argv)
{
    // Should trigger assert, but actually returns 1
    int retVal =  doSomethingWithEnum(static_cast<MyEnum>(4));
    std::cout << "RetVal=" << retVal << std::endl;

    return 0;
}
4b9b3361

Ответ 1

Ваш оператор switch будет скомпилирован в это (g++ 4.4.5):

    cmpl    $1, %eax
    je      .L3
    movl    $1, %eax
    jmp     .L4
.L3:
    movl    $2, %eax
.L4:
    leave
    ret

Как можно видеть, утверждение полностью оптимизировано, и компилятор выбирает сравнение с E2 и возвращает 1 во всех остальных случаях. С тремя значениями перечисления он не может этого сделать.

В разделе 5.2.9 стандарта С++ 98 (статический литье) приводятся причины этого:

Значение интегрального или перечисляемого типа может быть явно преобразовано в тип перечисления. Значение не изменяется если исходное значение находится в пределах значений перечисления (7.2). В противном случае результирующее значение перечисления unspeci фи-е изд.

Другими словами, компилятор может использовать любое требуемое значение перечисления (в этом случае E1), если вы попытаетесь использовать недопустимое значение. Это включает в себя выполнение интуитивно понятной вещи и использование предоставленной незаконной стоимости или использование разных значений в зависимости от обстоятельств, поэтому поведение изменяется в зависимости от количества значений перечисления.

Ответ 2

Это не похоже на ошибку компилятора. Это больше похоже на поведение undefined. В правиле говорится, что вы можете присвоить значение undefined Enum IF, это значение находится в диапазоне перечисления. В вашем случае компилятору нужен только один бит для представления перечисления, поэтому он, вероятно, делает какую-то оптимизацию.

Ответ 3

Это совершенно нормально, ваш код зависит от неуказанного поведения.

В С++ перечисление должно содержать значения между его более низкими и более высокими значениями. Все, что еще может быть преобразовано или проигнорировано компилятором.

Например, если у меня есть enum Foo { min = 10, max = 11 };, тогда компилятор может представить его одним значимым битом и добавить 10, когда я попрошу его распечатать или преобразовать в целое число.

В общем случае компиляторы не используют преимущества этого "уплотнения" из-за стоимости исполнения, они просто выбирают базовый тип, который может содержать все значения и 0. Чаще всего это int, если int слишком мало.

Однако это не мешает им предполагать, что значения, содержащиеся в этом int, ограничены определенным вами диапазоном. В вашем случае: [0, 2).

Таким образом, оператор switch можно просто оптимизировать в сравнении с 0, так как вы указали, что ваше перечисление может быть только 0 или 1.

В том же духе if (foo == 3) можно считать тавтологическим сравнением (всегда ложным) и оптимизированным.

Фактически, как описано в gcc-ошибке, сигнализируемой Хансом Пассатом, эта оптимизация обычно происходит при мощности границ 2s для gcc. Например, если у вас есть enum { zero, one, two, three }; и назначено 4 или выше, такое же поведение произойдет.

Обратите внимание, что фактически не сохраненное значение не влияет! Вот почему выражение печати работает так, как вы ожидали, и является источником путаницы.

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


Hans Passat предоставил gcc "bug" , который открыт для отслеживания этой "проблемы".

И Эмиль Штирке дал оправдание (вот обновленная цитата для скопированных перечислений):

5.2.9/10

Значение интегрального или перечисляемого типа может быть явно преобразовано в тип перечисления. Значение не изменяется, если исходное значение находится в диапазоне значений перечисления (7.2). В противном случае результирующее значение не указано (и может быть не в этом диапазоне). Значение типа с плавающей запятой также может быть преобразовано в тип перечисления. Полученное значение совпадает с преобразованием исходного значения в базовый тип перечисления (4.9), а затем в тип перечисления.