Quel est l'intérêt de noreturn?

189

[dcl.attr.noreturn] fournit l'exemple suivant:

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

mais je ne comprends pas à quoi ça sert [[noreturn]], car le type de retour de la fonction est déjà void.

Alors, quel est l'intérêt de l' noreturnattribut? Comment est-il censé être utilisé?

BЈовић
la source
1
Qu'est-ce qui est si important dans ce type de fonction (qui se produira probablement une fois lors de l'exécution d'un programme) qui mérite une telle attention? N'est-ce pas une situation facilement détectable?
user666412
1
@MrLister Les OP combinent les concepts de «retour» et de «valeur de retour». Étant donné qu'ils sont presque toujours utilisés en tandem, je pense que la confusion est justifiée.
Slipp D.Thompson

Réponses:

209

L'attribut noreturn est censé être utilisé pour les fonctions qui ne reviennent pas à l'appelant. Cela ne signifie pas des fonctions vides (qui retournent à l'appelant - elles ne renvoient tout simplement pas de valeur), mais des fonctions dans lesquelles le flux de contrôle ne reviendra pas à la fonction appelante une fois la fonction terminée (par exemple, les fonctions qui quittent l'application, boucle pour toujours ou lancer des exceptions comme dans votre exemple).

Cela peut être utilisé par les compilateurs pour effectuer des optimisations et générer de meilleurs avertissements. Par exemple, si fa l'attribut noreturn, le compilateur peut vous avertir d' g()être du code mort lorsque vous écrivez f(); g();. De même, le compilateur saura ne pas vous avertir des déclarations de retour manquantes après les appels à f().

sepp2k
la source
5
Et une fonction comme execvecelle -là ne devrait pas revenir mais pourrait ? Doit-il avoir l' attribut noreturn ?
Kalrish
22
Non, il ne devrait pas - s'il y a une possibilité pour le flux de contrôle de revenir à l'appelant, il ne doit pas avoir l' noreturnattribut. noreturnne peut être utilisé que si votre fonction est garantie de faire quelque chose qui termine le programme avant que le flux de contrôle puisse revenir à l'appelant - par exemple parce que vous appelez exit (), abort (), assert (0), etc.
RavuAlHemio
7
@ SlippD.Thompson Si un appel à une fonction noreturn est encapsulé dans un bloc try, tout code du bloc catch sera de nouveau considéré comme joignable.
sepp2k
2
@ sepp2k Cool. Il n'est donc pas impossible de revenir, juste anormal. C'est utile. À votre santé.
Slipp D.Thompson
7
@ SlippD.Thompson non, il est impossible de revenir. Lancer une exception ne revient pas, donc si chaque chemin est renvoyé, c'est bien noreturn. La gestion de cette exception n'est pas la même chose que son retour. Tout code dans le tryaprès l'appel est toujours inaccessible, et sinon, voidaucune affectation ou utilisation de la valeur de retour ne se produira.
Jon Hanna
63

noreturnn'indique pas au compilateur que la fonction ne renvoie aucune valeur. Il indique au compilateur que le flux de contrôle ne reviendra pas à l'appelant . Cela permet au compilateur de faire une variété d'optimisations - il n'a pas besoin de sauvegarder et de restaurer tout état volatil autour de l'appel, il peut éliminer le code mort tout code qui suivrait autrement l'appel, etc.

Stephen Canon
la source
29

Cela signifie que la fonction ne se terminera pas. Le flux de contrôle n'atteindra jamais l'instruction après l'appel à f():

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

Les informations peuvent être utilisées par le compilateur / optimiseur de différentes manières. Le compilateur peut ajouter un avertissement indiquant que le code ci-dessus est inaccessible, et il peut modifier le code réel de g()de différentes manières, par exemple pour prendre en charge les continuations.

David Rodríguez - Dribeas
la source
3
gcc / clang ne donne pas d'avertissements
TemplateRex
4
@TemplateRex: compilez avec -Wno-returnet vous recevrez un avertissement. Ce n'est probablement pas celui que vous attendiez, mais c'est probablement suffisant pour vous dire que le compilateur a connaissance de ce que [[noreturn]]c'est et qu'il peut en profiter. (Je suis un peu surpris que cela -Wunreachable-coden'ait pas
commencé
3
@TemplateRex: Désolé -Wmissing-noreturn, l'avertissement implique que l'analyse de flux a déterminé que le std::coutn'est pas accessible. Je n'ai pas une nouvelle gcc assez à portée de main pour regarder l'ensemble généré, mais je ne serais pas surpris si l'appel à operator<<été abandonné
David Rodríguez - dribeas
1
Voici un vidage d'assembly (-S -o - flags dans coliru), en effet, supprime le code "inaccessible". Fait intéressant, il suffit -O1déjà de supprimer ce code inaccessible sans [[noreturn]]indice.
TemplateRex
2
@TemplateRex: Tout le code est dans la même unité de traduction et visible, de sorte que le compilateur peut déduire le à [[noreturn]]partir du code. Si cette unité de traduction n'avait qu'une déclaration de la fonction qui a été définie ailleurs, le compilateur ne pourrait pas supprimer ce code, car il ne sait pas que la fonction ne retourne pas. C'est là que l'attribut devrait aider le compilateur.
David Rodríguez - dribeas
18

Les réponses précédentes expliquaient correctement ce qu'est noreturn, mais pas pourquoi il existe. Je ne pense pas que les commentaires "optimisation" soient le but principal: les fonctions qui ne retournent pas sont rares et n'ont généralement pas besoin d'être optimisées. Je pense plutôt que la principale raison d'être de noreturn est d'éviter les avertissements faussement positifs. Par exemple, considérez ce code:

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

Si abort () n'avait pas été marqué "noreturn", le compilateur aurait pu avertir que ce code avait un chemin où f ne renvoie pas un entier comme prévu. Mais comme abort () est marqué sans retour, il sait que le code est correct.

Nadav Har'El
la source
Tous les autres exemples listés utilisent des fonctions void - comment cela fonctionne-t-il lorsque vous avez à la fois la directive [[no return]] et un type de retour non void? La directive [[no return]] n'entre-t-elle en jeu que lorsque le compilateur est prêt à avertir d'une possibilité de ne pas retourner et ignorer l'avertissement? Par exemple, le compilateur dit-il: "OK, voici une fonction non vide." * continue la compilation * "Oh merde, ce code pourrait ne pas revenir! Dois-je avertir l'utilisateur? *" Nevermind, je vois la directive no-return. Continuer "
Raleigh L.
2
La fonction noreturn dans mon exemple n'est pas f (), c'est abort (). Cela n'a aucun sens de marquer une fonction non vide ni un retour. Une fonction qui renvoie parfois une valeur et parfois retourne (un bon exemple est execve ()) ne peut pas être marquée ni retour.
Nadav Har'El
1
implicit-fallthrough est un autre exemple: stackoverflow.com/questions/45129741/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
12

Type théoriquement parlant, voidc'est ce qu'on appelle dans d'autres langues unitou top. Son équivalent logique est True . Toute valeur peut être convertie légitimement en void(chaque type est un sous-type de void). Pensez-y comme un ensemble «univers»; il n'y a pas d'opérations communes à toutes les valeurs du monde, il n'y a donc pas d'opérations valides sur une valeur de type void. En d'autres termes, vous dire que quelque chose appartient à l'univers ne vous donne aucune information - vous le savez déjà. Donc, ce qui suit est sain:

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

Mais la mission ci-dessous n'est pas:

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]], D'autre part, que l' on appelle parfois empty, Nothing, Bottomou Botet est l'équivalent logique de faux . Il n'a aucune valeur du tout et une expression de ce type peut être transtypée en (c'est-à-dire est un sous-type de) n'importe quel type. Ceci est l'ensemble vide. Notez que si quelqu'un vous dit que «la valeur de l'expression foo () appartient à l'ensemble vide», c'est très informatif - cela vous dit que cette expression ne terminera jamais son exécution normale; il va abandonner, lancer ou se bloquer. C'est exactement le contraire de void.

Donc, ce qui suit n'a pas de sens (pseudo-C ++, car ce noreturnn'est pas un type C ++ de première classe)

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

Mais l'affectation ci-dessous est parfaitement légitime, car il throwest entendu par le compilateur de ne pas retourner:

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

Dans un monde parfait, vous pouvez utiliser noreturncomme valeur de retour pour la fonction raise()ci-dessus:

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

Malheureusement, C ++ ne le permet pas, probablement pour des raisons pratiques. Au lieu de cela, il vous permet d'utiliser un [[ noreturn ]]attribut qui aide à guider les optimisations et les avertissements du compilateur.

Elazar
la source
5
Rien ne peut être jeté à voidet voidn'évalue à trueou falseou quoi que ce soit d' autre.
Plus clair
6
Quand je dis true, je ne veux pas dire "la valeur truedu type bool" mais le sens logique, voir la correspondance Curry-Howard
Elazar
8
La théorie des types abstraite qui ne correspond pas au système de types d'un langage spécifique n'est pas pertinente lors de l'examen du système de types de ce langage. La question, en question :-), concerne le C ++, pas la théorie des types.
Plus clair
8
(void)true;est parfaitement valable, comme le suggère la réponse. void(true)est quelque chose de complètement différent, syntaxiquement. Il s'agit d'une tentative de créer un nouvel objet de type voiden appelant un constructeur avec truecomme argument; cela échoue, entre autres raisons, car ce voidn'est pas de première classe.
Elazar
2
Donc c'est. J'ai l'habitude d'utiliser le casting de type (valeur), pas le casting de style C.
Plus clair