Was ist der Sinn von Noreturn?

189

[dcl.attr.noreturn] bietet das folgende Beispiel:

[[ noreturn ]] void f() {
    throw "error";
    // OK
}

aber ich verstehe nicht, worum es geht [[noreturn]], weil der Rückgabetyp der Funktion bereits ist void.

Was ist der Sinn des noreturnAttributs? Wie soll es verwendet werden?

BЈовић
quelle
1
Was ist so wichtig an dieser Art von Funktion (die höchstwahrscheinlich einmal bei einer Programmausführung auftritt), die solche Aufmerksamkeit verdient? Ist das nicht eine leicht erkennbare Situation?
user666412
1
@MrLister Das OP verbindet die Konzepte von "Rückgabe" und "Rückgabewert". Angesichts der Tatsache, dass sie fast immer zusammen verwendet werden, halte ich die Verwirrung für gerechtfertigt.
Slipp D. Thompson

Antworten:

209

Das noreturn-Attribut soll für Funktionen verwendet werden, die nicht zum Aufrufer zurückkehren. Das bedeutet nicht, dass Funktionen ungültig sind (die zum Aufrufer zurückkehren - sie geben nur keinen Wert zurück), sondern Funktionen, bei denen der Kontrollfluss nach Beendigung der Funktion nicht zur aufrufenden Funktion zurückkehrt (z. B. Funktionen, die die Anwendung beenden, Schleife für immer oder Ausnahmen auslösen wie in Ihrem Beispiel).

Dies kann von Compilern verwendet werden, um einige Optimierungen vorzunehmen und bessere Warnungen zu generieren. Wenn beispielsweise fdas Attribut noreturn vorhanden ist, kann der Compiler Sie g()beim Schreiben davor warnen, dass der Code tot ist f(); g();. Ebenso kann der Compiler Sie nicht vor fehlenden return-Anweisungen nach Aufrufen von warnen f().

sepp2k
quelle
5
Was ist mit einer solchen Funktion execve, die nicht zurückkehren sollte, aber könnte ? Sollte es das noreturn- Attribut haben?
Kalrish
22
Nein, sollte es nicht - wenn es eine Möglichkeit gibt, dass der Kontrollfluss zum Aufrufer zurückkehrt, darf er das noreturnAttribut nicht haben . noreturndarf nur verwendet werden, wenn Ihre Funktion garantiert etwas tut, das das Programm beendet, bevor der Kontrollfluss zum Aufrufer zurückkehren kann - zum Beispiel, weil Sie exit (), abort (), assert (0) usw.
aufrufen
6
@ SlippD.Thompson Wenn ein Aufruf einer noreturn-Funktion in einen try-Block eingeschlossen ist, gilt jeder Code ab dem catch-Block wieder als erreichbar.
sepp2k
2
@ sepp2k Cool. Es ist also nicht unmöglich zurückzukehren, nur abnormal. Das ist nützlich. Prost.
Slipp D. Thompson
7
@ SlippD.Thompson nein, es ist unmöglich zurückzukehren. Das Auslösen einer Ausnahme wird nicht zurückgegeben. Wenn also jeder Pfad ausgelöst wird, ist dies der Fall noreturn. Die Behandlung dieser Ausnahme ist nicht die gleiche wie die zurückgegebene. Code innerhalb des trynach dem Aufruf ist immer noch nicht erreichbar, und wenn nicht, erfolgt voidkeine Zuweisung oder Verwendung des Rückgabewerts.
Jon Hanna
62

noreturnteilt dem Compiler nicht mit, dass die Funktion keinen Wert zurückgibt. Es teilt dem Compiler mit, dass der Kontrollfluss nicht zum Aufrufer zurückkehrt . Dies ermöglicht es dem Compiler, eine Vielzahl von Optimierungen vorzunehmen - er muss keinen flüchtigen Zustand um den Aufruf herum speichern und wiederherstellen, er kann jeglichen Code, der sonst dem Aufruf folgen würde, durch Deadcode eliminieren usw.

Stephen Canon
quelle
29

Dies bedeutet, dass die Funktion nicht abgeschlossen wird. Der Kontrollfluss wird die Anweisung nach dem Aufruf von f():

void g() {
   f();
   // unreachable:
   std::cout << "No! That's impossible" << std::endl;
}

Die Informationen können vom Compiler / Optimierer auf verschiedene Arten verwendet werden. Der Compiler kann eine Warnung hinzufügen, dass der obige Code nicht erreichbar ist, und er kann den tatsächlichen Code g()auf verschiedene Arten ändern, um beispielsweise Fortsetzungen zu unterstützen.

David Rodríguez - Dribeas
quelle
3
gcc / clang geben keine Warnungen
TemplateRex
4
@TemplateRex: Kompilieren Sie mit -Wno-returnund Sie erhalten eine Warnung. Wahrscheinlich nicht die, die Sie erwartet haben, aber es reicht wahrscheinlich aus, Ihnen mitzuteilen, dass der Compiler [[noreturn]]weiß, was ist, und dass er es nutzen kann. (Ich bin ein bisschen überrascht, dass -Wunreachable-codenicht
eingetreten ist
3
@TemplateRex: Entschuldigung -Wmissing-noreturn, die Warnung impliziert, dass die Flussanalyse festgestellt hat, dass der std::coutnicht erreichbar ist. Ich habe nicht genug GCC zur Hand, um mir die generierte Baugruppe anzusehen, aber ich wäre nicht überrascht, wenn der Anruf beioperator<<
David Rodríguez - Dribeas
1
Hier ist ein Assembly-Dump (-S -o - Flags in coliru), der tatsächlich den "nicht erreichbaren" Code löscht. Interessanterweise reicht -O1es bereits aus , diesen nicht erreichbaren Code ohne den [[noreturn]]Hinweis zu löschen.
TemplateRex
2
@TemplateRex: Der gesamte Code befindet sich in derselben Übersetzungseinheit und ist sichtbar, sodass der Compiler [[noreturn]]den Code daraus ableiten kann. Wenn diese Übersetzungseinheit nur eine Deklaration der Funktion hätte, die an einer anderen Stelle definiert wurde, könnte der Compiler diesen Code nicht löschen , da er nicht weiß, dass die Funktion nicht zurückgibt. Hier sollte das Attribut dem Compiler helfen.
David Rodríguez - Dribeas
17

Frühere Antworten haben richtig erklärt, was Noreturn ist, aber nicht, warum es existiert. Ich denke nicht, dass die "Optimierungs" -Kommentare der Hauptzweck sind: Funktionen, die nicht zurückkehren, sind selten und müssen normalerweise nicht optimiert werden. Ich denke eher, dass die Hauptaufgabe von noreturn darin besteht, falsch positive Warnungen zu vermeiden. Betrachten Sie beispielsweise diesen Code:

int f(bool b){
    if (b) {
        return 7;
    } else {
        abort();
    }
 }

Wäre abort () nicht als "noreturn" markiert worden, hätte der Compiler möglicherweise davor gewarnt, dass dieser Code einen Pfad hat, in dem f nicht wie erwartet eine Ganzzahl zurückgibt. Da abort () als no return markiert ist, weiß es, dass der Code korrekt ist.

Nadav Har'El
quelle
Alle anderen aufgeführten Beispiele verwenden ungültige Funktionen. Wie funktioniert dies, wenn Sie sowohl die Direktive [[no return]] als auch einen nicht ungültigen Rückgabetyp haben? Kommt die Direktive [[no return]] nur ins Spiel, wenn der Compiler bereit ist, vor einer Möglichkeit zu warnen, nicht zurückzukehren und die Warnung zu ignorieren? Zum Beispiel lautet der Compiler: "Okay, hier ist eine nicht leere Funktion." * setzt das Kompilieren fort * "Oh Mist, dieser Code wird möglicherweise nicht zurückgegeben! Soll ich den Benutzer warnen? *" Nevermind, ich sehe die No-Return-Direktive. Weiter "
Raleigh L.
2
Die noreturn-Funktion in meinem Beispiel ist nicht f (), sondern abort (). Es ist nicht sinnvoll, eine nicht leere Funktion noch zu markieren. Eine Funktion, die manchmal einen Wert zurückgibt und manchmal zurückgibt (ein gutes Beispiel ist execve ()), kann nicht als noreturn markiert werden.
Nadav Har'El
1
Implicit-Fallthrough ist ein weiteres Beispiel: stackoverflow.com/questions/45129741/…
Ciro Santilli 5 冠状 病 六四 六四 法轮功
11

Typ theoretisch gesprochen, voidheißt das in anderen Sprachen unitoder top. Sein logisches Äquivalent ist Wahr . Jeder Wert kann legitimiert umgewandelt werden void(jeder Typ ist ein Subtyp von void). Betrachten Sie es als "Universum" -Set; Es gibt keine gemeinsamen Operationen für alle Werte auf der Welt, daher gibt es keine gültigen Operationen für einen Wert vom Typ void. Anders ausgedrückt: Wenn Sie sagen, dass etwas zum Universum gehört, erhalten Sie keinerlei Informationen - Sie wissen es bereits. Das Folgende ist also Ton:

(void)5;
(void)foo(17); // whatever foo(17) does

Die folgende Zuordnung lautet jedoch nicht:

void raise();
void f(int y) {
    int x = y!=0 ? 100/y : raise(); // raise() returns void, so what should x be?
    cout << x << endl;
}

[[noreturn]], Auf der anderen Seite, manchmal genannt wird empty, Nothing, Bottomoder Botund ist die logische Äquivalent Falsch . Es hat überhaupt keine Werte, und ein Ausdruck dieses Typs kann in einen beliebigen Typ umgewandelt werden (dh ist ein Subtyp eines beliebigen Typs). Dies ist die leere Menge. Beachten Sie, dass jemand, der Ihnen sagt, dass "der Wert des Ausdrucks foo () zur leeren Menge gehört", sehr informativ ist - er sagt Ihnen, dass dieser Ausdruck niemals seine normale Ausführung abschließen wird. es wird abbrechen, werfen oder hängen. Es ist das genaue Gegenteil von void.

Das Folgende macht also keinen Sinn (Pseudo-C ++, da noreturnes sich nicht um einen erstklassigen C ++ - Typ handelt)

void foo();
(noreturn)5; // obviously a lie; the expression 5 does "return"
(noreturn)foo(); // foo() returns void, and therefore returns

Die folgende Zuordnung ist jedoch absolut legitim, da throwder Compiler versteht, dass er nicht zurückgibt:

void f(int y) {
    int x = y!=0 ? 100/y : throw exception();
    cout << x << endl;
}

In einer perfekten Welt können Sie noreturnals Rückgabewert für die raise()obige Funktion Folgendes verwenden:

noreturn raise() { throw exception(); }
...
int x = y!=0 ? 100/y : raise();

Leider erlaubt C ++ dies nicht, wahrscheinlich aus praktischen Gründen. Stattdessen können Sie [[ noreturn ]]Attribute verwenden, mit denen Sie Compileroptimierungen und Warnungen steuern können.

Elazar
quelle
5
Nichts darf gegossen voidund voidniemals bewertet werden trueoder falseoder irgendetwas anderes.
Klarer
5
Wenn ich sage true, meine ich nicht "den Wert truedes Typs bool", sondern den logischen Sinn, siehe Curry-Howard-Korrespondenz
Elazar
8
Eine abstrakte Typentheorie, die nicht zum Typensystem einer bestimmten Sprache passt, ist für die Erörterung des Typsystems dieser Sprache irrelevant. Die Frage, in Frage :-), betrifft C ++, nicht die Typentheorie.
Klarer
8
(void)true;ist vollkommen gültig, wie die Antwort nahelegt. void(true)ist syntaktisch etwas völlig anderes. Es ist ein Versuch, ein neues Objekt vom Typ zu erstellen, voidindem ein Konstruktor mit trueals Argument aufgerufen wird. Dies scheitert unter anderem daran, dass voides nicht erstklassig ist.
Elazar
2
So ist es. Ich bin es gewohnt, Typ- (Wert-) Casting zu verwenden, kein Casting im C-Stil.
Klarer