220 votes

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

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

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

mais je ne comprends pas quel est l'intérêt de [[noreturn]], car le type de retour de la fonction est déjà void.

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

2 votes

Qu'est-ce qui est si important à propos de ce genre de fonction (qui se produira très probablement une fois pendant l'exécution d'un programme) qui mérite une telle attention? N'est-ce pas une situation facilement détectable?

3 votes

@MrLister L'OP confond les concepts de "returning" et "return value". Étant donné qu'ils sont presque toujours utilisés ensemble, je pense que la confusion est justifiée.

254voto

sepp2k Points 157757

L'attribut [[noreturn]] est censé être utilisé pour les fonctions qui ne retournent pas à l'appelant. Cela ne signifie pas les fonctions void (qui retournent à l'appelant - elles ne retournent simplement pas de valeur), mais les fonctions où le flux de contrôle ne reviendra pas à la fonction appelante après que la fonction ait fini (par exemple, les fonctions qui quittent l'application, bouclent indéfiniment ou lancent des exceptions comme dans votre exemple).

Cela peut être utilisé par les compilateurs pour effectuer certaines optimisations et générer de meilleures avertissements. Par exemple, si f a l'attribut [[noreturn]], le compilateur pourrait vous avertir que g() est un code mort lorsque vous écrivez f(); g();. De même, le compilateur saura qu'il ne doit pas vous avertir des instructions de return manquantes après les appels à f().

6 votes

Que diriez-vous d'une fonction telle que execve qui ne devrait pas renvoyer mais pourrait le faire? Devrait-elle avoir l'attribut noreturn?

25 votes

Non, cela ne devrait pas être le cas -- s'il existe une possibilité pour le flux de contrôle de retourner à l'appelant, il ne doit pas avoir l'attribut noreturn. noreturn ne 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 retourner à l'appelant -- par exemple parce que vous appelez exit(), abort(), assert(0), etc.

2 votes

Cela inclut-il le retour via une exception throw (pour ainsi dire), ou les exceptions jetées ignorent-elles les catch en dehors de la fonction noreturn, ou est-il interdit de jeter des exceptions à partir de la fonction noreturn ?

71voto

Stephen Canon Points 58003

noreturn ne dit pas au compilateur que la fonction ne retourne aucune valeur. Il dit 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 volatile autour de l'appel, il peut éliminer tout code mort qui suivrait autrement l'appel, etc.

0 votes

Faites attention d'utiliser [[noreturn]]. Car si la fonction contient une boucle while, et que vous cassez la boucle involontairement, le programme peut agir de façon étrange.

32voto

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();
   // inaccessible :
   std::cout << "Non! C'est impossible" << std::endl;
}

L'information peut être utilisée par le compilateur/l'optimiseur de différentes manières. Le compilateur peut ajouter un avertissement 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.

3 votes

4 votes

@TemplateRex : Compilez avec -Wno-return et vous obtiendrez un avertissement. Probablement pas celui auquel vous vous attendiez, mais il est probablement suffisant pour vous indiquer que le compilateur a connaissance de ce qu'est [[noreturn]] et qu'il peut en tirer parti. (Je suis un peu surpris que -Wunreachable-code ne se soit pas déclenché...)

3 votes

@TemplateRex: Désolé -Wmissing-noreturn, l'avertissement implique que l'analyse du flux a déterminé que le std::cout n'est pas atteignable. Je n'ai pas sous la main un gcc suffisamment récent pour regarder l'assembly généré, mais je ne serais pas surpris si l'appel à operator<< était supprimé

26voto

Nadav Har'El Points 1591

Les réponses précédentes expliquaient correctement ce qu'est noreturn, mais pas pourquoi il existe. Je ne pense pas que les commentaires sur "l'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. Plutôt, je pense que la raison principale d'être de noreturn est d'éviter les avertissements de faux 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 émettre un avertissement concernant ce code ayant un chemin où f ne retourne pas un entier comme prévu. Mais parce que abort() est marqué comme noreturn, il sait que le code est correct.

0 votes

Tous les autres exemples répertorié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]] entre-t-elle en jeu uniquement lorsque le compilateur est prêt à avertir d'une possibilité de non retour et à ignorer l'avertissement ? Par exemple, le compilateur dirait-il : "D'accord, voici une fonction non void." * continue à compiler * "Oh mince, ce code pourrait ne pas renvoyer ! Dois-je avertir l'utilisateur ? * Peu importe, je vois la directive no return. Continuez"

5 votes

La fonction noreturn dans mon exemple n'est pas f(), c'est abort(). Il n'a pas de sens de marquer une fonction non-void comme noreturn. Une fonction qui retourne parfois une valeur et parfois ne retourne pas (un bon exemple est execve()) ne peut pas être marquée noreturn.

1 votes

Implicit-fallthrough est un autre exemple : stackoverflow.com/questions/45129741/…

16voto

Elazar Points 6342

De manière théorique, void est ce qui est appelé dans d'autres langues unit ou top. Son équivalent logique est True. Toute valeur peut légitimement être convertie 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 dans le monde, donc il n'y a pas d'opérations valides sur une valeur de type void. Autrement dit, vous dire que quelque chose appartient à l'ensemble univers ne vous donne aucune information - vous le savez déjà. Donc ce qui suit est correct:

(void)5;
(void)foo(17); // quoi que foo(17) fasse

Mais l'assignation ci-dessous ne l'est pas:

void raise();
void f(int y) {
    int x = y!=0 ? 100/y : raise(); // raise() renvoie void, donc quelle devrait être la valeur de x?
    cout << x << endl;
}

[[noreturn]], d'autre part, est parfois appelé empty, Nothing, Bottom ou Bot et est l'équivalent logique de False. Il n'a aucune valeur du tout, et une expression de ce type peut être convertie en (c'est-à-dire est un sous-type de) n'importe quel type. C'est l'ensemble vide. Notez que si quelqu'un vous dit "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; elle va avorter, lancer une exception ou se bloquer. C'est exactement l'opposé de void.

Donc ce qui suit n'a pas de sens (pseudo-C++, puisque noreturn n'est pas un type à part entière en C++)

void foo();
(noreturn)5; // clairement un mensonge; l'expression 5 "retourne"
(noreturn)foo(); // foo() renvoie void, et donc retourne

Mais l'affectation ci-dessous est parfaitement légitime, puisque throw est compris par le compilateur comme n'étant pas une fonction de retour:

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

Dans un monde parfait, vous pourriez utiliser noreturn comme 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 donne la possibilité d'utiliser l'attribut [[ noreturn ]] qui aide à guider les optimisations du compilateur et les avertissements.

6 votes

Rien ne peut être converti en void et void ne s'évalue jamais à true ou false ou quoi que ce soit d'autre.

2 votes

Tout peut être converti en void. et void n'est jamais évalué à true puisque bool est un sous-type de void et non l'inverse. Encore une fois, tout ce qui est dit ici est de nature théorique. rien de spécifique à C++. void n'est pas un citoyen de première classe dans le système de types C++.

8 votes

Lorsque je dis true, je ne veux pas dire "la valeur true du type bool", mais le sens logique, voir correspondance Curry-Howard

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X