Нужны ли атрибуты noreturn для выходящих функций?

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

Мне объяснили, что в контексте, таком как

void myexit(int s) _Noreturn {
   exit(s);
}
// ...
if (!p) { myexit(1); } 
f(*p);
/// ...

noreturn предотвращает оптимизацию ветвления !p. Но действительно ли разрешено компилятору оптимизировать эту ветвь? Я понимаю, что обоснование для его оптимизации было бы следующим: "Undefined поведение не может произойти. Если p == NULL, разыменованием является UB, поэтому p никогда не может быть NULL в этом контексте, поэтому ветвь !p не запускается". Но не может ли компилятор разрешить проблему так же, если предположить, что myexit может быть функцией, которая не возвращается (даже если она явно не помечена как таковая)?

Ответ 1

Это позволяет провести несколько оптимизаций. Во-первых, для самого вызова это может позволить упрощенную настройку, не все регистры должны быть сохранены, вместо call или аналогичного может использоваться инструкция jmp. Затем код после вызова также может быть оптимизирован, потому что нет возврата к нормальному потоку.

Итак, да, обычно _Noreturn является ценной информацией для компилятора.

Но, как прямой ответ на ваш вопрос, нет, это свойство для оптимизации, поэтому это необязательно.

Ответ 2

Аксиома: Стандарт - это определенный ресурс на том, что четко определено в C.

  • Стандарт определяет assert, поэтому использование assert четко определено.
  • assert условно вызывает функцию abort, a _Noreturn, поэтому это разрешено.
  • Каждое использование assert находится внутри функции. Поэтому функции могут возвращаться или не возвращаться.
  • В стандарте есть этот пример:

    _Noreturn void g (int i) { // causes undefined behavior if i <= 0
        if (i > 0) abort();
    }
    

    Поэтому функции условного возврата не должны быть _Noreturn. Это означает:

    • Для функций, определяемых извне, компилятор должен предположить, что функция может не вернуться и не имеет возможности оптимизировать if -branch
    • Для "внутренних" определенных функций компилятор может проверить, действительно ли функция всегда возвращает и оптимизирует ветвь.

В обоих случаях скомпилированное поведение программы совпадает с тем, что будет делать не оптимизирующая абстрактная машина C, и соблюдается правило "как есть".