Почему функция" 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 являются подсказка для компилятора, степень принятия определяется реализацией.

прежде всего,_Noreturn функции спецификатора (или noreturn, используя <stdnoreturn.h>) - это подсказка компилятору о теоретические обещание программиста, что эта функция никогда не вернет. Основываясь на этом обещании, компилятор может принимать определенные решения, выполнять некоторые оптимизации для генерации кода.

IIRC, если функция указана с noreturn спецификатор функции в конечном итоге возвращается вызывающему объекту, либо

  • С помощью и явные return сообщении
  • по достижении конца тела функции

the поведение неопределено. Вы НЕ ДОЛЖЕН выходим из функции.

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

теперь, в случае, если вы сделали обещание раньше и позже, решите нарушить это, результат-UB. Компиляторы рекомендуется, но не требуется, чтобы производить предупреждения, когда _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 ранее было объявлено с the 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() или аналогичный, поэтому он не возвращается.

что else будет ли ваша функция делать иначе, чем вернуться после того, как она вызвала printf()?

на Стандарт C на функции 6.7.4 спецификаторы пункт 12 в частности, включает в себя пример noreturn функция, которая может фактически возвращать-и помечает поведение как undefined:

Пример 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 или некоторую комбинацию.

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

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


на


как упоминали другие, Это классическое неопределенное поведение. Ты обещал!--0--> не вернулся, но вы все равно заставили его вернуться. Ты можешь собрать осколки, когда они разобьются.

хотя компилятор компилирует func в обычной манере (несмотря на ваш noreturn), то noreturn влияет на вызов функции.

вы можете увидеть это в списке сборки: компилятор предположил, в main, что func не вернется. Поэтому он буквально удалил все код после call func (смотрите сами на https://godbolt.org/g/8hW6ZR). Список сборок не усекается, он буквально заканчивается после call func потому что компилятор предполагает, что любой код после этого будет недоступен. Итак, когда func на самом деле вернется, main собирается начать выполнение любого дерьма, следующего за main функция - будь то заполнение, непосредственные константы или море 00 байт. Опять же-очень неопределенно поведение.

это транзитивная-функция, которая вызывает noreturn функция во всех возможных путях кода может сама по себе считаться noreturn.


по данным этой

Если функция объявлена _Noreturn возвращает, поведение не определено. Если это можно обнаружить, рекомендуется диагностика компилятора.

программист несет ответственность за то, чтобы эта функция никогда не возвращалась, например, выход(1) в конце функции.


ret просто означает, что функция возвращает значение управления обратно. Итак,main тут call func, CPU выполняет функцию, а затем, с ret, процессор продолжает выполнение main.

редактировать

таким образом,получается, noreturn не сделать функция не возвращается вообще, это просто спецификатор, который сообщает компилятору, что код этой функции записывается таким образом способ, которым функция не будет возвращать. Итак, вы должны убедиться, что эта функция на самом деле не возвращает управление вызываемому абоненту. Например, вы можете вызвать exit внутри него.

кроме того, учитывая то, что я прочитал об этом спецификаторе, кажется, что для того, чтобы убедиться, что функция не вернется к своей точке вызова, следует вызвать другое noreturn функция внутри него и убедитесь, что последний всегда работает (во избежание неопределенное поведение) и не вызывает сам UB.


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

см. пример здесь: 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.)

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 функции, потому что теряет backtrace info для таких вещей, как 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 этого не делают; это также пропущенная оптимизация.

но 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, это именно то, что мы получаем.

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