нет возврата в функцию с помощью оператора switch - PullRequest
0 голосов
/ 30 августа 2018

Я разрабатываю приложение в LINUX с более старой версией gcc (7. что-то, если я правильно помню). Недавно я попытался запустить то же самое приложение на Windows. В Windows я использую MinGW в качестве компилятора (с gcc 8.1.0).

Я столкнулся с этим сообщением об ошибке при компиляции моего приложения в Windows:

предупреждение: управление достигает конца не пустой функции [-Wreturn-type]

код похож на следующий:

class myClass {
protected:
    enum class myEnum{
        a,
        b,
    };

    int fun(myClass::myEnum e);
}

и

int myClass::fun(myClass::myEnum e) {
    switch (e){
        case myEnum::a:{
            return 0;
        }
        case myEnum::b:{
            return 1;
        }
    }
}

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

Является ли этот фрагмент кода действительно проблемой, и мне нужно добавить несколько фиктивных операторов возврата?

Существует ли ветвь этой функции, которая приведет к отсутствию оператора возврата?

Ответы [ 4 ]

0 голосов
/ 31 августа 2018

Вы должны иметь в виду, что в C ++ enum s не то, чем они кажутся. Они просто int с некоторыми ограничениями и могут легко принимать другие значения, отличные от указанных. Рассмотрим этот пример:

#include <iostream>

enum class MyEnum {
  A = 1,
  B = 2
};

int main() {
  MyEnum m {}; // initialized to 0
  switch(m) {
    case MyEnum::A: return 0;
    case MyEnum::B: return 0;
  }
  std::cout << "skipped all cases!" << std::endl; 
}

Обходной путь - это поставить default кейс с assert(false), как указано VTT выше , или (если вы можете дать всем гарантию, что никакие значения за пределами указанного набора никогда не будут получите) используйте подсказку компилятора, например __builtin_unreachable() в GCC и clang:

  switch(m) {
    case MyEnum::A: return 0;
    case MyEnum::B: return 0;
    default: __builtin_unreachable();
  }
0 голосов
/ 31 августа 2018

Во-первых, вы описываете предупреждение, а не сообщение об ошибке. Компиляторы не обязаны выдавать такие предупреждения, и им по-прежнему разрешается успешно компилировать ваш код - поскольку это технически допустимо.

Практически, большинство современных компиляторов могут выдавать такие предупреждения, но в конфигурации по умолчанию они не делают. При использовании gcc компилятор может быть дополнительно настроен на выдачу таких предупреждений (например, с использованием подходящих параметров командной строки).

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

Большинство компиляторов проводят обширный анализ кода либо напрямую (во время синтаксического анализа исходного кода), либо путем анализа некоторого внутреннего представления этого кода. Анализ необходим для определения наличия в коде диагностируемых ошибок, чтобы определить, как оптимизировать производительность.

Из-за такого анализа большинство компиляторов могут и действительно обнаруживают ситуации, которые могут быть проблематичными, даже если код не содержит диагностируемых ошибок (т. Е. Он "достаточно корректен", чтобы стандарт C ++ не требовал диагностики).

В этом случае существует ряд различных выводов, к которым может прийти компилятор, в зависимости от того, как он проводит анализ.

  • Существует switch. В принципе, код после оператора switch может быть выполнен.
  • Код после переключения достигает конца функции без return, и функция возвращает значение. Результатом этого является потенциальное неопределенное поведение.

Если анализ компилятора зашел так далеко (и компилятор настроен на предупреждение о таких вещах), критерии для выдачи предупреждения будут выполнены. Необходим дальнейший анализ, если предупреждение может быть подавлено, например, определите, что все возможные значения e представлены case, и что во всех случаях есть оператор return. Дело в том, что поставщик компилятора может решить не проводить такой анализ и, следовательно, не подавлять предупреждения по разным причинам.

  • Выполнение дополнительного анализа увеличивает время компиляции. Производители конкурируют между собой, утверждая, что их компилятор работает быстрее, поэтому НЕ делать некоторый анализ, поэтому выгодно сократить время компиляции;
  • Поставщик компилятора может посчитать, что лучше отмечать потенциальные проблемы, даже если код на самом деле правильный. Учитывая выбор между выдачей посторонних предупреждений или не предупреждением о некоторых вещах, продавец может предпочесть давать посторонние предупреждения.

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

В конце концов, вам нужно рассматривать предупреждения компилятора как признак потенциальных проблем, а затем принимать разумное решение о том, стоит ли беспокоиться о потенциальной проблеме. Ваши варианты отсюда включают подавление предупреждения (например, использование параметра командной строки, который вызывает подавление предупреждения), изменение кода для предотвращения предупреждения (например, добавление return после случая switch и / или default в switch, который возвращается).

0 голосов
/ 31 августа 2018

Следует быть очень осторожным, опуская операторы return. Это неопределенное поведение:

9.6.3 Оператор возврата [stmt.return]

Выход из конца конструктора, деструктора или функции с возвращаемым типом cv void эквивалентен возвращению без операнда. В противном случае выпадение из конца функции, отличной от main (6.6.1), приводит к неопределенному поведению.

Может показаться заманчивым, что этот код подходит, потому что все допустимые значения перечислителя (в данном случае в диапазоне 0..1 [0..(2 ^ M - 1)] с M = 1) обрабатываются в switch, однако компилятору не требуется выполнять любой конкретный анализ достижимости, чтобы выяснить это, прежде чем прыгать в зону UB.

Более того, пример из ответа SergeyA показывает, что этот вид кода является бомбой замедленного действия:

class myClass {
protected:
    enum class myEnum{
        a,
        b,
        c
    };

    int fun(myClass::myEnum e);
};

int myClass::fun(myClass::myEnum e) {
    switch (e){
        case myEnum::a:{
            return 0;
        }
        case myEnum::b:{
            return 1;
        }
        case myEnum::c:{
            return 2;
        }
    }
}

Просто добавив третий член перечисления (и обработав его в switch), диапазон допустимых значений перечислителя будет расширен до 0..3 ([0..(2 ^ M - 1)] с M = 2), и clang с радостью примет его без каких-либо претензий, даже если передача 3 в эту функцию пропустит переключение, потому что компилятору также не требуется сообщать UB.

Таким образом, практическим правилом будет написание кода таким образом, чтобы все пути заканчивались либо функцией return throw, либо [[noreturn]]. В этом конкретном случае я бы, вероятно, написал один оператор return с assertion для необработанных значений перечислителя:

int myClass::fun(myClass::myEnum e) {
    int result{};
    switch (e){
        case myEnum::a:{
            result = 0;
            break;
        }
        case myEnum::b:{
            result = 1;
            break;
        }
        default:
        {
           assert(false);
           break;
        }
    }
    return result;
}
0 голосов
/ 30 августа 2018

Это недостаток статического анализатора g ++. Не учитывается тот факт, что все значения перечисления правильно обрабатываются в операторе switch.

Здесь вы можете заметить https://godbolt.org/z/LQnBNi, что clang не выдает никаких предупреждений для кода в его текущей форме и выдает два предупреждения («не все значения enum обрабатываются в switch» и «элементы управления достигают конца») on non-void function "), когда к перечислению добавляется другое значение.

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

...