Какой смысл в noreturn?


189

[dcl.attr.noreturn] предоставляет следующий пример:

[[ noreturn ]] void f() {
    throw "error";
    // OK
}

но я не понимаю в чем смысл [[noreturn]], потому что возвращаемый тип функции уже есть void.

Итак, в чем смысл этого noreturnатрибута? Как это должно быть использовано?


1
Что такого важного в такого рода функции (которая, скорее всего, произойдет однажды при выполнении программы), которая заслуживает такого внимания? Разве это не легко обнаруживаемая ситуация?
— user666412

1
@MrLister ОП объединяет понятия «возвращение» и «возвращаемое значение». Учитывая, как они почти всегда используются в тандеме, я думаю, что путаница оправдана.
— Слипп Д. Томпсон

Ответы:


209

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

Это может быть использовано компиляторами для оптимизации и генерирования более качественных предупреждений. Например, если fесть атрибут noreturn, компилятор может предупредить вас о том, g()что вы не работаете, когда пишете f(); g();. Точно так же компилятор будет знать, чтобы не предупреждать вас о пропущенных операторах возврата после вызовов f().


5
Как насчет такой функции, execveкоторая не должна возвращаться, но могла бы ? Должен ли он иметь атрибут noreturn ?
— Калриш

22
Нет, это не должно - если есть возможность для потока управления вернуться к вызывающей стороне, он не должен иметь noreturnатрибут. noreturnможет использоваться, только если ваша функция гарантированно сделает что-то, что завершит программу, прежде чем поток управления сможет вернуться к вызывающей стороне - например, потому что вы вызываете exit (), abort (), assert (0) и т. д.
— RavuAlHemio

7
@ SlippD.Thompson Если вызов функции noreturn заключен в блок try, любой код из блока catch снова будет считаться достижимым.
— sepp2k

2
@ sepp2k Круто. Так что вернуться невозможно, просто ненормально. Это полезно Приветствия.
— Слипп Д. Томпсон

7
@ SlippD. Томпсон нет, вернуть невозможно. Бросок исключения не возвращается, так что если каждый путь выбрасывает, то это так noreturn. Обработка этого исключения не совпадает с возвратом. Любой код в tryпосле после вызова все еще недоступен, и если нет, voidто никакого присвоения или использования возвращаемого значения не произойдет.
— Джон Ханна

63

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


29

Это означает, что функция не будет завершена. Поток управления никогда не попадет в оператор после вызова f():

void g() {
   f();
   // unreachable:
   std::cout << "No! That's impossible" << std::endl;
}

Информация может использоваться компилятором / оптимизатором различными способами. Компилятор может добавить предупреждение о том, что приведенный выше код недоступен, и он может модифицировать фактический код g()различными способами, например, для поддержки продолжений.



4
@TemplateRex: скомпилируйте с, -Wno-returnи вы получите предупреждение. Вероятно, не тот, который вы ожидали, но достаточно сказать, что компилятор знает, что [[noreturn]]есть, и он может воспользоваться этим. (Я немного удивлен, что -Wunreachable-codeне
— пнул

3
@TemplateRex: Извините -Wmissing-noreturn, предупреждение подразумевает, что анализ потока определил, что std::coutон недоступен. У меня недостаточно нового gcc, чтобы посмотреть на сгенерированную сборку, но я не удивлюсь, если вызов to operator<<будет отброшен
— Дэвид Родригес - dribeas

1
Вот дамп сборки (-S -o - flags in coliru), действительно удаляющий «недоступный» код. Интересно, -O1что уже достаточно, чтобы сбросить этот недоступный код без [[noreturn]]подсказки.
— TemplateRex

2
@TemplateRex: весь код находится в одном и том же модуле перевода и является видимым, поэтому компилятор может вывести его [[noreturn]]из кода. Если бы у этого модуля преобразования было только объявление функции, которая была определена где-то еще, компилятор не смог бы удалить этот код, поскольку он не знает, что функция не возвращает. Вот где атрибут должен помочь компилятору.
— Дэвид Родригес - dribeas

18

Предыдущие ответы правильно объяснили, что такое noreturn, но не почему он существует. Я не думаю, что «оптимизационные» комментарии являются основной целью: функции, которые не возвращаются, редки и обычно не нуждаются в оптимизации. Скорее, я думаю, что основной смысл норетуры - избегать ложноположительных предупреждений. Например, рассмотрим этот код:

int f(bool b){
    if (b) {
        return 7;
    } else {
        abort();
    }
 }

Если бы abort () не был помечен как «noreturn», компилятор мог бы предупредить, что у этого кода есть путь, где f не возвращает целое число, как ожидалось. Но поскольку abort () помечен как «нет возврата», он знает, что код верен.


Во всех других перечисленных примерах используются функции void - как это работает, когда у вас есть и директива [[no return]], и тип возврата, отличный от void? Директива [[no return]] вступает в действие только тогда, когда компилятор готов предупредить о возможности не возврата и игнорировать предупреждение? Например, идет ли компилятор: «Хорошо, здесь не пустая функция». * продолжает компиляцию * «О, дерьмо, этот код может не вернуться! Должен ли я предупредить пользователя? *» Не важно, я вижу директиву без возврата. Продолжайте "
— Роли Л.

2
Функция noreturn в моем примере это не f (), это abort (). Не имеет смысла отмечать не пустую функцию noreturn. Функция, которая иногда возвращает значение, а иногда возвращает (хорошим примером является execve ()), не может быть помечена как noreturn.
— Nadav Har'El

1
implicit-fallthrough является еще одним таким примером: stackoverflow.com/questions/45129741/…
— Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

12

Тип теоретически говоря, voidэто то , что называется на других языках unitили top. Его логический эквивалент - Истина . Любое значение может быть законно приведено к void(каждый тип является подтипом void). Думайте об этом как "вселенная"; нет никаких общих операций для всех значений в мире, поэтому нет допустимых операций для значения типа void. Другими словами, говоря вам, что что-то принадлежит множеству вселенных, вы не получите никакой информации - вы уже это знаете. Итак, следующее звучит правильно:

(void)5;
(void)foo(17); // whatever foo(17) does

Но задание ниже не является:

void raise();
void f(int y) {
    int x = y!=0 ? 100/y : raise(); // raise() returns void, so what should x be?
    cout << x << endl;
}

[[noreturn]], С другой стороны, иногда называют empty, Nothing, Bottomили Botи является логическим эквивалентом False . У него вообще нет значений, и выражение этого типа может быть приведено (то есть является подтипом) любого типа. Это пустой набор. Обратите внимание, что если кто-то скажет вам, что «значение выражения foo () принадлежит пустому набору», это очень информативно - это говорит о том, что это выражение никогда не завершит свое нормальное выполнение; это прервет, бросит или повесит. Это полная противоположность void.

Таким образом, следующее не имеет смысла (псевдо-C ++, так как noreturnэто не первоклассный тип C ++)

void foo();
(noreturn)5; // obviously a lie; the expression 5 does "return"
(noreturn)foo(); // foo() returns void, and therefore returns

Но приведенное ниже присваивание совершенно законно, поскольку throwкомпилятор понимает, что оно не возвращает:

void f(int y) {
    int x = y!=0 ? 100/y : throw exception();
    cout << x << endl;
}

В идеальном мире вы могли бы использовать noreturnв качестве возвращаемого значения для функции raise()выше:

noreturn raise() { throw exception(); }
...
int x = y!=0 ? 100/y : raise();

К сожалению, C ++ не позволяет этого, вероятно, по практическим соображениям. Вместо этого он дает вам возможность использовать [[ noreturn ]]атрибут, который помогает направлять оптимизацию компилятора и предупреждения.


5
Ничто не может быть приведен к типу voidи voidникогда не имеет значение trueили falseили что - нибудь еще.
— яснее

6
Когда я говорю true, я имею в виду не «значение trueтипа bool», а логический смысл, см. Соответствие Карри-Ховарда
— Elazar

8
Абстрактная теория типов, которая не соответствует системе типов конкретного языка, не имеет значения при обсуждении системы типов этого языка. Вопрос, о котором идет речь :-), касается C ++, а не теории типов.
— яснее

8
(void)true;совершенно правильно, как и предполагает ответ. void(true)это что-то совершенно другое, синтаксически. Это попытка создать новый объект типа void, вызвав конструктор с trueаргументом; это терпит неудачу, среди других причин, потому что voidэто не первый класс.
— Elazar

2
Так что, это. Я привык использовать приведение типа (значения), а не приведение к стилю.
— яснее
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.