Почему функция" noreturn " возвращается?
прочитал этой вопрос о noreturn
атрибут, который используется для функций, которые не возвращаются вызывающему объекту.
тогда я сделал программу в C.
#include <stdio.h>
#include <stdnoreturn.h>
noreturn void func()
{
printf("noreturn funcn");
}
int main()
{
func();
}
и генерируется сборка кода с помощью этой:
.LC0:
.string "func"
func:
pushq %rbp
movq %rsp, %rbp
movl $.LC0, %edi
call puts
nop
popq %rbp
ret // ==> Here function return value.
main:
pushq %rbp
movq %rsp, %rbp
movl , %eax
call func
почему функция func()
вернуться после предоставления ?
9 ответов:
спецификаторы функций в C являются a подсказка для компилятора, степень принятия определяется реализацией.
прежде всего,
_Noreturn
функции спецификатора (илиnoreturn
, используя<stdnoreturn.h>
) - это подсказка компилятору о теоретические обещание программиста, что эта функция никогда не вернет. Основываясь на этом обещании, компилятор может принимать определенные решения, выполнять некоторые оптимизации для генерации кода.IIRC, если функция указана с
noreturn
спецификатор функции в конечном итоге возвращается к своему вызывающему объекту, либо
- С помощью и явные
return
сообщении- по достижении конца тела функции
the поведение неопределено. Ты НЕЛЬЗЯ выходим из функции.
чтобы было понятно, используя
noreturn
описатель функции не мешает форма функции возвращаясь к своему абоненту. Это обещание, данное программистом компилятору, чтобы позволить ему еще немного свободы для генерации оптимизированного кода.теперь, в случае, если вы сделали обещание раньше и позже, решите нарушить это, результат UB. Компиляторы рекомендуется, но не требуется, чтобы производить предупреждения, когда a
_Noreturn
функция, по-видимому, способна вернуться к своему вызывающему объекту.согласно главе §6.7.4,
C11
пункт 8функция, объявленная с помощью
_Noreturn
спецификатор функции не должен возвращаться к вызывающему объекту.и, пункт 12, (обратите внимание на комментарии!!)
EXAMPLE 2 _Noreturn void f () { abort(); // ok } _Noreturn void g (int i) { // causes undefined behavior if i <= 0 if (i > 0) abort(); }
на
C++
, поведение очень похоже. Цитируя главу §7.6.4,C++14
пункт 2 (выделено мной)если функция
f
называется, гдеf
ранее было объявлено с элементnoreturn
атрибут иf
в конце концов возвращает, поведение не определено.[ Примечание: функция может завершиться, вызвав исключение. -конец Примечание ][ Примечание: реализации рекомендуется выдавать предупреждение, если функция помечена
[[noreturn]]
может возвращаться. -конец Примечание ]3 [ пример:
[[ noreturn ]] void f() { throw "error"; // OK } [[ noreturn ]] void q(int i) { // behavior is undefined if called with an argument <= 0 if (i > 0) throw "positive"; }
-конец примера ]
почему функция func () возвращается после предоставления атрибута noreturn?
потому что вы написали код, который сказал это.
если вы не хотите, чтобы ваша функция возвращает, вызов
exit()
илиabort()
или аналогичный, поэтому он не возвращается.что другое будет ли ваша функция делать иначе, чем вернуться после того, как он вызвал
printf()
?The Стандарт C на функции 6.7.4 спецификаторы пункт 12 в частности, включает в себя пример
noreturn
функция, которая может фактически вернуть-и помечает поведение как неопределено:Пример 2
_Noreturn void f () { abort(); // ok } _Noreturn void g (int i) { // causes undefined behavior if i<=0 if (i > 0) abort(); }
короче,
noreturn
это ограничение это вы на месте код код - это говорит компилятору "мой код никогда не вернется". Если вы нарушите это ограничение, это все на вас.
noreturn
- Это обещание. Вы говорите компилятору: "это может быть или не быть очевидным, но Я знаю, основываясь на том, как я написал код, что эта функция никогда не вернет."Таким образом, компилятор может избежать настройки механизмов, которые позволили бы функции вернуться должным образом. Выходя из этих механизмов может позволить компилятору генерировать более эффективный код.как функция может не возвращаться? Одним из примеров было бы, если бы он назывался
exit()
вместо.но если вы обещаете компилятору, что ваша функция не вернется, и компилятор не организует, чтобы функция могла вернуться правильно, а затем вы идете и пишете функцию, которая тут return, что компилятор должен делать? Он в основном имеет три возможности:
- быть "хорошим" для вас и выяснить, как правильно вернуть функцию в любом случае.
- испускают код, который, когда функция неправильно возвращается, он падает или ведет себя произвольно непредсказуемым образом.
- дать вам предупреждение или сообщение об ошибке, указывающее, что вы нарушили свое обещание.
компилятор может сделать 1, 2, 3, или некоторая комбинация.
Если это звучит как неопределенное поведение, потому что это.
как уже упоминалось, это классическое неопределенное поведение. Ты обещал
func
не вернется, но вы все равно заставили его вернуться. Ты должен собрать осколки, когда это сломается.хотя компилятор компилирует
func
в обычном порядке (несмотря на свойnoreturn
), тоnoreturn
влияет на вызов функции.вы можете увидеть это в списке сборок: компилятор предположил, в
main
, чтоfunc
не вернется. Поэтому он буквально удалил все код послеcall func
(смотрите сами на https://godbolt.org/g/8hW6ZR). список сборок не усекается, он буквально заканчивается послеcall func
потому что компилятор предполагает, что любой код после этого будет недоступен. Итак, когдаfunc
на самом деле вернется,main
собирается начать выполнять все, что следуетmain
функция-будь то заполнение, непосредственные константы или море00
байт. Опять же-очень неопределенно поведение.это транзитивная функция, которая вызывает a
noreturn
функция во всех возможных путях кода может сама по себе считатьсяnoreturn
.
по данным этой
Если функция объявлена _Noreturn возвращает, поведение не определено. Если это можно обнаружить, рекомендуется выполнить диагностику компилятора.
это ответственность программиста, чтобы убедиться, что эта функция никогда не возвращается, например, выход(1) в конце функции.
ret
просто означает, что функция возвращает значение управления обратно. Итак,main
тутcall func
, процессор выполняет функцию, а затем, сret
, процессор продолжает выполнениеmain
.Edit
таким образом,получается,
noreturn
не сделать функция не возвращается вообще, это просто спецификатор, который сообщает компилятору, что код этой функции написан таким образом способ, которым функция не будет возвращать. Итак, что вы должны сделать здесь, чтобы убедиться, что эта функция на самом деле не возвращает управление обратно к вызываемому. Например, вы можете позвонитьexit
внутри него.кроме того, учитывая то, что я читал об этом спецификаторе, кажется, что для того, чтобы убедиться, что функция не вернется к своей точке вызова, следует вызвать другое
noreturn
функция внутри него и убедитесь, что последний всегда работает (во избежание неопределенное поведение) и не вызывает сам UB.
функция No return не сохраняет регистры на записи, поскольку это не обязательно. Это упрощает оптимизацию. Отлично подходит для рутины планировщика, например.
Смотрите пример здесь: https://godbolt.org/g/2N3THC и определить разницу
TL: DR: это пропущенная оптимизация gcc.
noreturn
- это обещание компилятору, что функция не вернет. Это позволяет оптимизировать и полезно, особенно в тех случаях, когда компилятору трудно доказать, что цикл никогда не выйдет, или иным образом доказать, что нет пути через функцию, которая возвращает.GCC уже оптимизирует
main
чтобы упасть с конца функции, еслиfunc()
возвращает, даже с по умолчанию-O0
(минимальный уровень оптимизации), что он выглядит как раньше.вывод
func()
сам по себе может считаться пропущенной оптимизацией; он может просто опустить все после вызова функции (поскольку вызов не возвращается-это единственный способ, которым сама функция может бытьnoreturn
). Это не отличный пример, так какprintf
является стандартной функцией C, которая, как известно, возвращается нормально (если выsetvbuf
датьstdout
буфер, который будет обработка выхода онлайн / оффлайн?)позволяет использовать другую функцию, о которой компилятор не знает.
void ext(void); //static int foo; _Noreturn void func(int *p, int a) { ext(); *p = a; // using function args after a function call foo = 1; // requires save/restore of registers } void bar() { func(&foo, 3); }
(код + x86-64 asm на godbolt compiler explorer.)
gcc7.2 выход для
bar()
это интересно. Это в строкахfunc()
, и исключаетfoo=3
мертвый магазин, оставив просто:bar: sub rsp, 8 ## align the stack call ext mov DWORD PTR foo[rip], 1 ## fall off the end
Gcc по-прежнему предполагает, что
ext()
собирается вернуться, иначе он мог бы просто хвост-называетсяext()
Сjmp ext
. Но gcc не tailcallnoreturn
функции, потому что теряет информацию об обратном пути для таких вещей, какabort()
. По-видимому, встраивание их в порядке, хотя.Gcc можно было бы оптимизировать, опустив
mov
магазин послеcall
как хорошо. Еслиext
возвращает, программа изливается, так что нет смысла генерировать любой из этого кода. Clang делает эту оптимизацию вbar()
/main()
.
это более интересная и большая пропущенная оптимизация.
gcc и clang оба испускают почти то же самое:
func: push rbp # save some call-preserved regs push rbx mov ebp, esi # save function args for after ext() mov rbx, rdi sub rsp, 8 # align the stack before a call call ext mov DWORD PTR [rbx], ebp # *p = a; mov DWORD PTR foo[rip], 1 # foo = 1 add rsp, 8 pop rbx # restore call-preserved regs pop rbp ret
эта функция может предполагать, что она не возвращается, и использовать
rbx
иrbp
без сохранения / восстановления их.Gcc для ARM32 фактически делает это, но все же выдает инструкции для возврата в противном случае чисто. Так что
noreturn
функция, которая фактически возвращается на ARM32, сломает ABI и вызовет проблемы с отладкой в звонящий или позже. (Неопределенное поведение позволяет это, но это по крайней мере проблема качества реализации:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82158.)это полезная оптимизация в тех случаях, когда gcc не может доказать, возвращает ли функция или нет. (Это, очевидно, вредно, когда функция просто возвращается. Gcc предупреждает, когда он уверен, что функция noreturn возвращается.) Другие целевые архитектуры gcc этого не делают; это также a пропущенная оптимизация.
но gcc не заходит достаточно далеко: оптимизация инструкции возврата также (или замена ее незаконной инструкцией) сэкономит размер кода и гарантирует шумный отказ вместо бесшумного повреждения.
и если вы собираетесь оптимизировать
ret
, оптимизация прочь все, что только необходимо, если функция будет возвращать имеет смысл.таким образом,
func()
может быть скомпилирован к:sub rsp, 8 call ext # *p = a; and so on assumed to never happen ud2 # optional: illegal insn instead of fall-through
каждая другая присутствующая инструкция является пропущенной оптимизацией. Если
ext
объявленnoreturn
, это именно то, что мы получаем.любой базовый блок что заканчивается с возвратом можно предположить, что никогда не будет достигнуто.