Почему функция" 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 63

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. дать вам предупреждение или сообщение об ошибке, указывающее, что вы нарушили свое обещание.

компилятор может сделать 1, 2, 3, или некоторая комбинация.

Если это звучит как неопределенное поведение, потому что это.

The

как уже упоминалось, это классическое неопределенное поведение. Ты обещал 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 не tailcall noreturn функции, потому что теряет информацию об обратном пути для таких вещей, как 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, это именно то, что мы получаем.

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