Warum kehrt die Funktion "noreturn" zurück?

72

Ich habe diese Frage zum noreturnAttribut gelesen , das für Funktionen verwendet wird, die nicht zum Aufrufer zurückkehren.

Dann habe ich ein Programm in C gemacht.

#include <stdio.h>
#include <stdnoreturn.h>

noreturn void func()
{
        printf("noreturn func\n");
}

int main()
{
        func();
}

Und Montage des Codes erzeugt unter Verwendung dieser :

.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    $0, %eax
        call    func

Warum kehrt die Funktion func()nach der Angabe des noreturnAttributs zurück?

msc
quelle
22
Bitte verzeihen Sie mir, dass ich gefragt habe, aber wie ist diese Funktion "noreturn" für irgendjemanden aus der Ferne nützlich? Wofür wird es verwendet / missbraucht?
Martin James
15
@MartinJames, Ihre Frage sieht aus wie ein Narr dies : P
ForceBru
17
"Ich habe diese Frage zum noreturnAttribut in C gelesen " Nein. Die von Ihnen verknüpfte Frage bezieht sich auf " noreturnAttribut in C ++", eine andere Sprache.
Gerhardh
12
Mit @MartinJames noreturn kann der Compiler einige Optimierungen vornehmen. Am wichtigsten ist, dass die Fehlerbehandlung, die mit exitoder gleichwertig endet, Blattfunktionen nicht in Nicht-Blattfunktionen verwandelt. Sie können also assertin den tiefsten Tiefen Ihres leistungskritischen Codes oder ähnliches haben und es kostet nicht mehr als eine korrekt vorhergesagte Verzweigung (anstatt dass die Funktion einen Frame einrichten, Register speichern, vom Anrufer gespeicherte Register vermeiden usw.). .
Art
5
"Hier Funktionsrückgabewert." Nicht wirklich; es tut nicht. Der x86- retBefehl bedeutet lediglich, dass die Ausführung zurück an den Aufrufer übertragen wird. Sie würden dasselbe für eine Funktion mit einem Rückgabewert von sehen void. Mit anderen Worten, es gibt die Kontrolle zurück. kein Wert. Jetzt geben alle x86-Aufrufkonventionen Werte im EAXRegister zurück, aber der Aufrufer und der Angerufene müssen sich darauf einigen. Wenn die Funktion zurückgibt voidoder nichts, enthält EAXsie nur Müll. Wie die Antworten bereits sagten, ist das Verhalten gemäß dem C-Sprachstandard undefiniert, aber genau das bedeutet der asm.
Cody Grey

Antworten:

121

Die Funktionsspezifizierer in C sind ein Hinweis auf den Compiler, der Akzeptanzgrad ist implementierungsdefiniert.

Zuallererst ist der _NoreturnFunktionsspezifizierer (oder noreturnverwenden <stdnoreturn.h>) ein Hinweis für den Compiler auf ein theoretisches Versprechen des Programmierers, dass diese Funktion niemals zurückkehren wird. Basierend auf diesem Versprechen kann der Compiler bestimmte Entscheidungen treffen und einige Optimierungen für die Codegenerierung vornehmen.

IIRC, wenn eine mit dem Funktionsbezeichner angegebene noreturnFunktion schließlich auch zu ihrem Aufrufer zurückkehrt

  • durch Verwendung und explizite returnAnweisung
  • durch Erreichen des Endes des Funktionskörpers

Das Verhalten ist undefiniert . Sie dürfen NICHT von der Funktion zurückkehren.

Um dies zu verdeutlichen, verhindert die Verwendung des noreturnFunktionsbezeichners nicht, dass ein Funktionsformular zu seinem Aufrufer zurückkehrt. Es ist ein Versprechen des Programmierers an den Compiler, ihm mehr Freiheit bei der Generierung von optimiertem Code zu geben.

Falls Sie früher und später ein Versprechen abgegeben haben, entscheiden Sie sich dafür, dies zu verletzen. Das Ergebnis ist UB. Compiler werden aufgefordert, aber nicht aufgefordert, Warnungen zu erstellen, wenn eine _NoreturnFunktion in der Lage zu sein scheint, zu ihrem Aufrufer zurückzukehren.

Gemäß Kapitel §6.7.4 C11, Absatz 8

Eine mit einem Funktionsspezifizierer deklarierte _NoreturnFunktion darf nicht zu ihrem Aufrufer zurückkehren.

und der Absatz 12 ( Beachten Sie die Kommentare !! )

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

Denn C++das Verhalten ist ziemlich ähnlich. Zitat aus Kapitel §7.6.4, C++14Absatz 2 ( Hervorhebung von mir )

Wenn eine Funktion faufgerufen wird, fdie zuvor mit dem noreturnAttribut deklariert wurde , und fschließlich zurückgegeben wird, ist das Verhalten undefiniert. [Hinweis: Die Funktion wird möglicherweise durch Auslösen einer Ausnahme beendet. - Endnote]

[Hinweis: Implementierungen werden aufgefordert, eine Warnung auszugeben, wenn eine markierte Funktion [[noreturn]]möglicherweise zurückgegeben wird. - Endnote]

3 [Beispiel:

[[ 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";
}

- Beispiel beenden]

Sourav Ghosh
quelle
4
@MartinJames Bitte beschuldigen Sie mich nicht, Sir, ich hatte nichts mit der "Existenz" dieser ... Funktion zu tun. :)
Sourav Ghosh
20
@MartinJames - Diese Funktion ist für statische Analysetools von großem Nutzen, insbesondere für Tools, die eine Stapeltiefenanalyse im ungünstigsten Fall durchführen. Ohne das noreturnAttribut einer Funktion, die wirklich nicht zurückgegeben wird, wären die Ergebnisse solcher Tools falsch. Dies kann auch bei Optimierungen hilfreich sein (bei jedem Aufruf einer solchen Funktion werden höchstens einige Bytes eingespart), da der Compiler die zurückgegebene Funktion nicht berücksichtigen muss. Der Kontrollfluss innerhalb einer aufrufenden Funktion hört an diesem Punkt einfach auf.
Phonetagger
36
tl; dr: noreturnbedeutet nicht "diese Funktion wird nicht zurückkehren"; es bedeutet "dem Compiler mitteilen, dass er davon ausgehen kann, dass diese Funktion nicht zurückkehrt". Es ist nicht da, um Ihre Arbeit zu erleichtern, es ist da, um die Arbeit des Compilers zu erleichtern.
Hong Ooi
15
@MartinJames: Stellen Sie sich das so vor: Es ist buchstäblich das Problem, herauszufinden, ob eine Funktion zurückkehrt oder nicht . Es ist jedoch auch eine interessante Eigenschaft, sowohl für die Korrektheit als auch für die Optimierung. Was können wir also tun, wenn wir eine Eigenschaft haben, über die wir entscheiden möchten, die aber tatsächlich die stereotype unentscheidbare Eigenschaft ist? Wir müssen den Programmierer fragen. Beachten Sie, dass Sprachen mit ausdrucksstärkeren Typsystemen als C normalerweise auch Typen dafür haben. ZB hat Scala einen Typ Unitfür eine Funktion, die nichts zurückgibt, und einen Typ Nothingfür eine Funktion, die nicht zurückgibt .
Jörg W Mittag
4
@MauganRa Deshalb habe ich vorgeschlagen ud2, IMO dies (Abbruch des Programms, das gleiche wird in bestimmten anderen Fällen getan, die statisch als undefiniertes Verhalten nachgewiesen werden können) wäre "schöner" als die Rückkehr zu einem Anrufer, der optimiert wurde, um es anzunehmen Gewohnheit.
Random832
50

Warum kehrt die Funktion func () zurück, nachdem das Attribut noreturn angegeben wurde?

Weil Sie Code geschrieben haben, der es gesagt hat.

Wenn Sie nicht Ihre Funktion zurückzukehren, rufenden exit()oder abort()o.ä. so zurückgibt es nicht.

Was würde Ihre Funktion anders tun, als nach dem Aufruf zurückzukehren printf()?

Der C-Standard in 6.7.4 Funktionsspezifizierer , Absatz 12 enthält speziell ein Beispiel für eine noreturnFunktion, die tatsächlich zurückkehren kann - und kennzeichnet das Verhalten als undefiniert :

BEISPIEL 2

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

Kurz gesagt, noreturnist eine Einschränkung , dass Sie auf platzieren Ihren Code - es teilt den Compiler „MY - Code wird nicht immer zurückkehren“ . Wenn Sie gegen diese Einschränkung verstoßen, liegt das ganz bei Ihnen.

Andrew Henle
quelle
6
@phonetagger Es ist völlig legal, dass ein Programm mit undefiniertem Verhalten ein Signal in der Zeit zurücksendet, das den Compiler beendet, bevor es das Programm produzieren kann. (Dies kann von der Physik nicht erlaubt sein , aber der Standard hat kein Problem damit.)
Ray
7
@phonetagger Tatsächlich wird die Häufigkeit, mit der Sie sagen können: "Ja, ich weiß, genau genommen ist es undefiniert, aber in der Praxis wird nichts Schlimmes passieren", im Laufe der Zeit immer kleiner. Je schlauer die Compiler werden, desto mehr Spielraum scheinen sie sich zu gewähren, um wirklich verrückte Sachen zu machen, wenn Sie ihnen eine Entschuldigung geben.
Steve Summit
3
@phonetagger: Hier ist ein echtes Beispiel für überraschende UB: godbolt.org/g/uZibdL kürzlich veröffentlichtes clang ++ kompiliert nicht leere Funktionen, die ohne returnEndlosschleifen enden , anscheinend absichtlich, um Ihnen zu helfen, den Fehler zu erkennen, wenn Sie die Warnung verpasst haben (oder vielleicht Fälle, in denen nicht gewarnt wurde, weil nicht sicher war, ob der Weg jemals eingeschlagen werden würde). Dies ist für C nicht der Fall, da es in C99 / C11 nur UB ist, wenn der Anrufer das Ergebnis verwendet .
Peter Cordes
4
@phonetagger Wir verwenden die phantasievollen (und denkwürdigen ) Beispiele nicht, weil wir tatsächlich glauben, dass UB zeitreisende lila Drachen dazu bringt, die Nasendämonen in einen explodierenden Kühlschrank zu jagen. Wir tun dies, um die Leute davon abzuhalten, anzunehmen, dass die UB auf eine bestimmte Weise arbeiten muss , weil alle anderen Optionen lächerlich sind. "Selbst lächerliche Ergebnisse sind möglich und keine Annahmen sind sicher", wollen wir vermitteln. Einige UB-Manifestationen sind so seltsam, dass niemand sie erwarten würde. (Ich habe diesen Kommentar nicht einmal geschrieben; ich habe gerade ein Programm ausgeführt, das enthielt, i = i++;und es erschien .)
Ray
2
Im Gegensatz dazu beantwortete Andrew die Frage des OP in seinem Eröffnungssatz direkt. Die Antwort lautet nicht: „Da es UB ist, kann es alles tun . Heilige Kuh, wie es der Zufall wollte, kehrte sie zurück! Was für ein Glück ! " Die Antwort lautet: Es wurde zurückgegeben, weil der OP-Code dies vorschreibt. Natürlich wäre jede Antwort nachlässig, ohne die UB zu erwähnen, was auch Andrew tut. Aber so wie es jetzt ist, ist Souravs bearbeitete Antwort jetzt die bessere Antwort.
Phonetagger
27

noreturnist ein Versprechen. Sie sagen dem Compiler: "Es mag offensichtlich sein oder auch nicht, aber ich weiß, basierend auf der Art und Weise, wie ich den Code geschrieben habe, dass diese Funktion niemals zurückkehren wird." Auf diese Weise kann der Compiler vermeiden, die Mechanismen einzurichten, mit denen die Funktion ordnungsgemäß zurückkehren kann. Wenn Sie diese Mechanismen weglassen, kann der Compiler möglicherweise effizienteren Code generieren.

Wie kann eine Funktion nicht zurückkehren? Ein Beispiel wäre, wenn es exit()stattdessen aufgerufen würde.

Wenn Sie dem Compiler jedoch versprechen, dass Ihre Funktion nicht zurückgegeben wird, und der Compiler nicht dafür sorgt, dass die Funktion ordnungsgemäß zurückgegeben wird, schreiben Sie eine Funktion, die zurückgibt, was der Compiler tun soll machen? Grundsätzlich gibt es drei Möglichkeiten:

  1. Seien Sie "nett" zu Ihnen und finden Sie heraus, wie Sie die Funktion trotzdem ordnungsgemäß zurückgeben können.
  2. Geben Sie Code aus, der bei unsachgemäßer Rückgabe abstürzt oder sich auf willkürlich unvorhersehbare Weise verhält.
  3. Geben Sie eine Warnung oder eine Fehlermeldung, die darauf hinweist, dass Sie Ihr Versprechen gebrochen haben.

Der Compiler kann 1, 2, 3 oder eine Kombination ausführen.

Wenn dies nach undefiniertem Verhalten klingt, liegt das daran, dass es so ist.

Das Fazit beim Programmieren wie im wirklichen Leben lautet: Machen Sie keine Versprechen, die Sie nicht halten können. Jemand anderes hat möglicherweise Entscheidungen auf der Grundlage Ihres Versprechens getroffen, und es können schlimme Dinge passieren, wenn Sie dann Ihr Versprechen brechen.

Steve Summit
quelle
1
Mit anderen Worten, lügen Sie den Compiler nicht an.
Jens
16

Das noreturnAttribut ist ein Versprechen, das Sie dem Compiler bezüglich Ihrer Funktion geben.

Wenn Sie tun Rückkehr von einer solchen Funktion, Verhalten ist nicht definiert, aber das bedeutet nicht , ein vernünftiger Compiler Sie zu verwirren der Anwendung , den Status ermöglicht vollständig durch das Entfernen retAussage, zumal der Compiler oft sogar in der Lage sein wird, abzuleiten , dass Eine Rückgabe ist in der Tat möglich.

Wenn Sie jedoch Folgendes schreiben:

noreturn void func(void)
{
    printf("func\n");
}

int main(void)
{
    func();
    some_other_func();
}

dann ist es für den Compiler durchaus vernünftig, das some_other_funckomplett zu entfernen , wenn es sich danach anfühlt.

Groo
quelle
Über das Durcheinander mit dem Status der Anwendung habe ich irgendwann ein Programm erstellt, in dem die Hauptfunktion keine return-Anweisung hatte, und die einzige Möglichkeit, sie zu schließen, bestand darin, sich abzumelden (sie würde sich nicht selbst schließen, ich konnte nicht X. es aus (es gab aus irgendeinem Grund ein leeres Grafikfenster), ich konnte es nicht erzwingen, es über den Task-Manager oder das Terminal zu beenden ...). Übrigens war dies unter Windows nicht sicher, welcher Compiler.
Solomon Ucko
11

Wie andere bereits erwähnt haben, ist dies ein klassisches undefiniertes Verhalten. Du hast versprochen func, nicht zurückzukehren, aber du hast es trotzdem geschafft. Sie können die Stücke aufheben, wenn das kaputt geht.

Obwohl der Compiler func(trotz Ihrer noreturn) auf die übliche Weise kompiliert , noreturnwirkt sich dies auf die aufrufenden Funktionen aus.

Sie können dies in der Assembly-Liste sehen: Der Compiler hat angenommen main, dass dies funcnicht zurückkehren wird. Daher wurde buchstäblich der gesamte Code nach dem gelöscht call func(überzeugen Sie sich selbst unter https://godbolt.org/g/8hW6ZR ). Die Assembly-Liste wird nicht abgeschnitten, sondern endet buchstäblich erst nach dem, call funcda der Compiler davon ausgeht, dass Code danach nicht mehr erreichbar ist. Wenn also functatsächlich zurückkehrt, mainwird mit der Ausführung des Mistes begonnen, der der mainFunktion folgt - sei es Auffüllen, unmittelbare Konstanten oder ein Meer von 00Bytes. Wieder - sehr undefiniertes Verhalten.

Dies ist transitiv - eine Funktion, die eine noreturnFunktion in allen möglichen Codepfaden aufruft, kann selbst angenommen werden noreturn.

nneonneo
quelle
Ja. Ihr Compiler ist die Person, die Sie wirklich niemals anlügen sollten.
Daniel Fischer
Huh, das ist komisch. noreturnscheint die Tailcall-Optimierung zu deaktivieren (und das Inlining zu stoppen, wenn die Noreturn-Funktion printf aufruft). Sie können dies sogar sehen mit -O3: godbolt.org/g/vFWp8E . Clang 4.0 und gcc 7.2 sind hier gleich.
Peter Cordes
Ah, das liegt daran, dass das noreturnHandling von gcc die Tailcall-Optimierung für bessere abort()Backtraces speziell deaktiviert : gcc.gnu.org/bugzilla/show_bug.cgi?id=55747#c4 .
Peter Cordes
8

Nach dieser

Wenn die als _Noreturn deklarierte Funktion zurückgegeben wird, ist das Verhalten undefiniert. Eine Compilerdiagnose wird empfohlen, wenn dies erkannt werden kann.

Es liegt in der Verantwortung des Programmierers, sicherzustellen, dass diese Funktion niemals zurückkehrt, z. B. exit (1) am Ende der Funktion.

ChrisB
quelle
6

retbedeutet einfach, dass die Funktion die Kontrolle an den Anrufer zurückgibt . In diesem mainFall call funcführt die CPU die Funktion aus und setzt dann mit retdie CPU die Ausführung von fort main.

Bearbeiten

Also, es stellt sich heraus , noreturnnicht machen die Funktion überhaupt nicht zurückkehren, es ist nur ein Spezifizierer, die den Compiler sagt , dass der Code dieser Funktion in einer solchen Art und Weise geschrieben , dass die Funktion wird bestätigt . Sie sollten hier also sicherstellen, dass diese Funktion die Kontrolle nicht an den Angerufenen zurückgibt. Zum Beispiel könnten Sie darin anrufen exit.

Angesichts dessen, was ich über diesen Bezeichner gelesen habe, scheint es, dass man, um sicherzustellen, dass die Funktion nicht zu ihrem Aufrufpunkt zurückkehrt, eine andere noreturn Funktion darin aufrufen und sicherstellen sollte, dass letztere immer ausgeführt wird (in der richtigen Reihenfolge) um undefiniertes Verhalten zu vermeiden) und verursacht UB selbst nicht.

ForceBru
quelle
1
Das ist wahr , aber Sie sollten lesen Sie dieses
Kocica
@ FilipKočica, das ist genau das , was ich gelesen habe, als Sie diesen Kommentar gepostet haben
ForceBru
3
@ user694733 Ich glaube, der Kern der Frage ist, dass der Fragesteller erwartet hat noreturn, etwas zu tun, das die Rückgabe seines Codes verhindert, anstatt noreturntatsächlich zu beschränken, was der von ihm geschriebene Code unter dem Standard tun darf .
Andrew Henle
Die Kette von noreturnmuss irgendwo enden, zum Beispiel eine Ausnahme auslösen , aufrufen longjmp, einen Systemaufruf wie machen _exit(2)oder etwas Hackiges mit Inline-Asm machen. Aber ja, es sei denn, Sie tun etwas anderes, das nicht zurückkehrt, sollten Sie eine andere noreturnFunktion aufrufen (einschließlich exit(3)oder abort(3).)
Peter Cordes
6

Keine Rückgabefunktion speichert die Register im Eintrag nicht, da dies nicht erforderlich ist. Dies erleichtert die Optimierung. Ideal zum Beispiel für die Scheduler-Routine.

Sehen Sie sich das Beispiel hier an: https://godbolt.org/g/2N3THC und erkennen Sie den Unterschied

P__J unterstützt Frauen in Polen
quelle
Das einzige, was optimiert werden kann, noreturnsind ein paar (!) Bytes von der ausführbaren Größe: noreturnFunktionen können niemals Teil des leistungskritischen Pfads in einem Code sein, der mindestens halbwegs gesund ist. Im gesamten Konstrukt geht es viel mehr darum, unnötige Warnungen zum Schweigen zu bringen als um die Optimierung.
cmaster
@cmaster Keine Attribute machen Code vernünftig, nur das Wissen des Codierers.
P__J unterstützt Frauen in Polen
Das ist nicht was ich sagte. Was ich gesagt habe ist: Wenn der Code mindestens halbwegs gesund ist, bedeutet dies, dass es keine Auswirkungen auf die Leistung von gibt noreturn. Das ist die entgegengesetzte Richtung des Denkens von dem, was Sie anscheinend verstanden haben. Ich würde niemals sagen, dass die Verwendung von noreturnCode mehr oder weniger vernünftig macht.
cmaster
That's not what I said.Sie haben zu komplizierte Denkweise für mich. Ich verstehe diesen "halbwegs gesunden" Gedanken nicht
P__J unterstützt Frauen in Polen
Ich hatte einfach das Problem, dass das Schreiben von " noreturnFunktionen können niemals Teil des leistungskritischen Pfades sein" falsch gewesen wäre: Es ist durchaus möglich, ein Programm zu schreiben, das noreturnAufrufe im leistungskritischen Pfad enthält (mittels longjmp()). Diese Programme werden jedoch unweigerlich geisteskrank sein. Die Bedingung "Code, der mindestens halbwegs gesund ist" schließt diese einfach aus. Hoffe das klärt die Dinge. (Entschuldigung für meine verwirrend präzise Sprache.)
cmaster - Monica
2

TL: DR: Es ist eine Fehloptimierung durch gcc .


noreturnist ein Versprechen an den Compiler, dass die Funktion nicht zurückkehren wird. Dies ermöglicht Optimierungen und ist insbesondere in Fällen nützlich, in denen es für den Compiler schwierig ist, zu beweisen, dass eine Schleife niemals beendet wird, oder auf andere Weise zu beweisen, dass es keinen Pfad durch eine zurückkehrende Funktion gibt.

GCC optimiert bereits, um maindas Ende der Funktion zu func()verlieren, wenn zurückgegeben wird, selbst mit der Standardeinstellung -O0(Mindestoptimierungsstufe), die Sie verwendet haben.

Die Ausgabe für func()sich könnte als verpasste Optimierung angesehen werden. es könnte einfach alles nach dem Funktionsaufruf weglassen (da der Aufruf nicht zurückgegeben werden kann, ist die einzige Möglichkeit, wie die Funktion selbst sein kann noreturn). Es ist kein gutes Beispiel, da printfeine Standard-C-Funktion bekanntermaßen normal zurückkehrt (es sei denn, Sie setvbufgeben stdouteinen Puffer an, der fehlerhaft ist?).

Verwenden wir eine andere Funktion, die der Compiler nicht kennt.

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);
}

( Code + x86-64 asm im Godbolt-Compiler-Explorer . )

Die Ausgabe von gcc7.2 für bar()ist interessant. Es inline func()und beseitigt den foo=3toten Speicher, so dass nur:

bar:
    sub     rsp, 8    ## align the stack
    call    ext
    mov     DWORD PTR foo[rip], 1
   ## fall off the end

Gcc geht weiterhin davon aus, dass ext()nach Rückkehr wird, sonst könnte es nur Schwanz genannt hat ext()mit jmp ext. Aber gcc ruft keine Tailcall- noreturnFunktionen auf, da dadurch Backtrace-Informationen für Dinge wie verloren gehen abort(). Anscheinend ist es in Ordnung, sie zu inlinieren.

Gcc hätte optimieren können, indem der movLaden auch nach dem weggelassen wurde call. Wenn extzurückgegeben wird, wird das Programm abgespritzt, sodass es keinen Sinn macht, diesen Code zu generieren. Clang macht diese Optimierung in bar()/ main().


funcselbst ist interessanter und eine größere verpasste Optimierung .

gcc und clang strahlen fast dasselbe aus:

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

Diese Funktion kann davon ausgehen, dass sie nicht zurückkehrt und verwendet rbxund rbpohne sie zu speichern / wiederherzustellen.

Gcc für ARM32 macht das tatsächlich, gibt aber immer noch Anweisungen aus, um ansonsten sauber zurückzukehren. Eine noreturnFunktion, die tatsächlich auf ARM32 zurückkehrt, unterbricht den ABI und verursacht schwer zu debuggende Probleme im Aufrufer oder später. (Undefiniertes Verhalten erlaubt dies, aber es handelt sich zumindest um ein Problem mit der Qualität der Implementierung: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82158 .)

Dies ist eine nützliche Optimierung in Fällen, in denen gcc nicht beweisen kann, ob eine Funktion zurückkehrt oder nicht. (Es ist jedoch offensichtlich schädlich, wenn die Funktion einfach zurückkehrt. Gcc warnt, wenn sicher ist, dass eine Noreturn-Funktion zurückkehrt.) Andere gcc-Zielarchitekturen tun dies nicht. Das ist auch eine verpasste Optimierung.

Aber gcc geht nicht weit genug: Wenn Sie auch die Rückgabeanweisung wegoptimieren (oder durch eine unzulässige Anweisung ersetzen), sparen Sie Code und garantieren einen lauten Fehler anstelle einer stillen Beschädigung.

Und wenn Sie das retoptimieren möchten, ist es sinnvoll, alles zu optimieren , was nur benötigt wird, wenn die Funktion zurückkehrt.

So func()könnte zusammengestellt werden zu :

    sub     rsp, 8
    call    ext
    # *p = a;  and so on assumed to never happen
    ud2                 # optional: illegal insn instead of fall-through

Jede andere vorhandene Anweisung ist eine verpasste Optimierung. Wenn extdeklariert noreturnwird, bekommen wir genau das.

Es kann davon ausgegangen werden, dass jeder Basisblock , der mit einer Rückgabe endet, niemals erreicht wird.

Peter Cordes
quelle