74 votes

Pourquoi la fonction "noreturn" revient-elle ?

Je lis este question sur noreturn qui est utilisé pour les fonctions qui ne retournent pas à l'appelant.

Puis j'ai fait un programme en C.

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

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

int main()
{
        func();
}

Et l'assemblage généré du code en utilisant este :

.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

Pourquoi la fonction func() retour après avoir fourni noreturn attribut ?

22 votes

Pardonnez-moi de vous poser la question, mais en quoi cette fonction de "non-retour" est-elle utile à qui que ce soit ? À quoi sert-elle ou est-elle mal utilisée ?

0 votes

Je veux dire que j'écris souvent des fonctions qui ne reviennent jamais (par exemple, une boucle 'while(true){};' dans un thread d'application), mais pourquoi le compilateur devrait-il le savoir ?

0 votes

N'est-ce pas comme l'option "sans roues" lors de la commande d'une nouvelle voiture ?

122voto

Sourav Ghosh Points 54713

Les spécificateurs de fonction en C sont un indice au compilateur, le degré d'acceptation est défini par l'implémentation.

Tout d'abord, _Noreturn spécificateur de fonction (ou, noreturn en utilisant <stdnoreturn.h> ) est un indice pour le compilateur à propos d'un fichier promesse théorique fait par le programmeur que cette fonction ne reviendra jamais. Sur la base de cette promesse, le compilateur peut prendre certaines décisions, effectuer certaines optimisations pour la génération du code.

IIRC, si une fonction spécifiée avec noreturn Le spécificateur de fonction retourne finalement à son appelant, soit

  • en utilisant et en explicitant return déclaration
  • en atteignant la fin de la fonction corps

le site le comportement est indéfini . Vous NE DOIT PAS retour de la fonction.

Pour que ce soit clair, utiliser noreturn spécificateur de fonction ne s'arrête pas une forme de fonction retournant à son appelant. Il s'agit d'une promesse faite par le programmeur au compilateur de lui laisser un peu plus de liberté pour générer un code optimisé.

Maintenant, dans le cas où vous avez fait une promesse plus tôt et plus tard, choisissez de la violer, le résultat est UB. Les compilateurs sont encouragés, mais pas obligés, de produire des avertissements lorsqu'un fichier _Noreturn semble être capable de retourner à son appelant.

Selon le chapitre §6.7.4, C11 Paragraphe 8

Une fonction déclarée avec un _Noreturn ne doit pas retourner à son appelant.

et, le paragraphe 12, ( Notez les commentaires ! )

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

Pour C++ le comportement est assez similaire. Citation du chapitre §7.6.4, C++14 paragraphe 2 ( c'est moi qui souligne )

Si une fonction f est appelé là où f était précédemment déclarée avec le noreturn et f éventuellement retourne, le comportement est indéfini. [Note : La fonction peut se terminer par la levée d'une exception. -fin note ]

[Note : Les implémentations sont encouragées à émettre un avertissement si une fonction marquée [[noreturn]] pourrait retourner. -note de fin ]

3 [ Exemple :

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

-fin de l'exemple ]

1 votes

@MartinJames : Je ressens la même chose pour gcc pure / const qui, en gros, indiquent simplement que le développeur promesses pour faire une fonction qui n'aura pas d'effets secondaires, ou bien nonnull qui est une promesse que vous ne passerez pas un pointeur nul à la fonction afin que le compilateur ( lui permettant d'éliminer toute null les vérifications que vous pourriez faire à l'intérieur de la fonction ).

20 votes

@MartinJames - Cette fonctionnalité est d'une grande utilité pour les outils d'analyse statique, en particulier ceux qui effectuent une analyse de la profondeur de pile dans le pire des cas. Sans la noreturn sur une fonction qui ne retournera vraiment pas, les résultats de ces outils seraient incorrects. Il s'agit de peut peut également être utile pour les optimisations (en économisant quelques octets au maximum, chaque fois qu'une telle fonction est appelée) car le compilateur ne doit pas tenir compte du retour de la fonction ; le flux de contrôle dans une fonction appelante s'arrête simplement à ce moment-là.

36 votes

Tl;dr : noreturn ne signifie pas "cette fonction ne reviendra pas" ; cela signifie "dire au compilateur qu'il peut supposer que cette fonction ne reviendra pas". Il n'est pas là pour faire votre travail plus facile, il est là pour rendre le du compilateur plus facile.

50voto

Andrew Henle Points 5156

Pourquoi la fonction func() retourne-t-elle après avoir fourni l'attribut noreturn ?

Parce que tu as écrit le code qui lui a dit de le faire.

Si vous ne voulez pas que votre fonction retourne, appelez exit() o abort() ou similaire pour qu'il ne revienne pas.

Quoi sinon votre fonction ferait-elle autre chose que de revenir après avoir appelé printf() ?

Le site C Standard en 6.7.4 Spécification des fonctions Le paragraphe 12 comprend spécifiquement un exemple de noreturn qui peut effectivement retourner - et qualifie le comportement de indéfini :

EXEMPLE 2

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

En bref, noreturn es un restriction que vous place sur votre code - il indique au compilateur "MON code ne reviendra jamais" . Si vous violez cette restriction, c'est votre faute.

0 votes

Bien que la réponse de Sourav soit plus complète d'un point de vue technique, cette réponse résume mieux le problème. À mon avis, c'est la meilleure réponse ! "Parce que vous avez écrit le code qui lui a dit de le faire."

0 votes

@phonetagger aucun doute, c'est une réponse très ciblée et parfaitement à propos. Le problème majeur de la question est qu'avec un morceau de code impliquant UB, il n'y a aucune garantie qu'il sera construit pour produire un binaire, du tout. Traitez tous les avertissements comme des erreurs et vous n'aurez pas de retour, du tout :)

0 votes

@SouravGhosh - Je ne suis pas d'accord sur le fait qu'il n'y ait aucune garantie que cela produise un binaire, à moins que (comme vous le suggérez) le compilateur soit configuré pour traiter tous les avertissements comme des erreurs. "UB" est par rapport à l'exécutable compilé, pas par rapport au compilateur lui-même. Mais la question était simple : "Pourquoi la fonction func() retour après avoir fourni noreturn attribut ?" Votre réponse est pertinente, mais ne répond pas vraiment à la question en soi. Celle-ci le fait : "Parce que vous avez écrit le code qui lui a dit de le faire." La vôtre répond à une question hypothétique différente : "Pourquoi mon noreturn planter le programme quand il revient ?"

27voto

Steve Summit Points 16971

noreturn est une promesse. Vous dites au compilateur, "Cela peut ou non être évident, mais I je sais, d'après la façon dont j'ai écrit le code, que cette fonction ne reviendra jamais". De cette façon, le compilateur peut éviter de mettre en place les mécanismes qui permettraient à la fonction de revenir correctement. Le fait de ne pas tenir compte de ces mécanismes pourrait permettre au compilateur de générer un code plus efficace.

Comment une fonction peut-elle ne pas revenir ? Par exemple, si elle appelle exit() à la place.

Mais si vous promettez au compilateur que votre fonction ne retournera pas, et que le compilateur ne s'arrange pas pour que la fonction puisse retourner correctement, et que vous écrivez une fonction qui fait retour, qu'est ce que le compilateur est censé faire ? Il a essentiellement trois possibilités :

  1. Soyez "gentil" avec vous et trouvez un moyen pour que la fonction revienne quand même correctement.
  2. Émettre du code qui, lorsque la fonction renvoie de manière inappropriée, se plante ou se comporte de manière arbitrairement imprévisible.
  3. vous donner un avertissement ou un message d'erreur indiquant que vous n'avez pas tenu votre promesse.

Le compilateur peut faire 1, 2, 3, ou une combinaison des deux.

Si cela ressemble à un comportement indéfini, c'est parce que c'est le cas.

L'essentiel, en programmation comme dans la vie réelle, c'est : Ne faites pas de promesses que vous ne pouvez pas tenir. Quelqu'un d'autre pourrait avoir pris des décisions sur la base de votre promesse, et de mauvaises choses peuvent arriver si vous ne tenez pas votre promesse.

1 votes

En d'autres termes, ne mentez pas au compilateur.

16voto

Groo Points 19453

Le site noreturn est une promesse que vous faire au compilateur au sujet de votre fonction.

Si vous faire retournent d'une telle fonction, le comportement est indéfini, mais cela ne signifie pas qu'un compilateur sain d'esprit vous permettra d'altérer complètement l'état de l'application en supprimant la fonction ret d'autant plus que le compilateur sera souvent capable de déduire qu'un retour est effectivement possible.

Cependant, si vous écrivez ceci :

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

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

alors il est tout à fait raisonnable pour le compilateur de supprimer l'élément some_other_func complètement, si on en a l'impression.

0 votes

En ce qui concerne la modification de l'état de l'application, à un moment donné, j'ai créé un programme dans lequel la fonction principale n'avait pas de déclaration de retour, et la fonction uniquement La seule façon de le fermer était de se déconnecter (il ne se fermait pas tout seul, je ne pouvais pas le sortir par X (il y avait une fenêtre graphique vide pour une raison quelconque), je ne pouvais pas le quitter de force via le gestionnaire des tâches ou le terminal...). BTW, c'était sous Windows, je ne suis pas sûr du compilateur.

11voto

nneonneo Points 56821

Comme d'autres l'ont mentionné, il s'agit d'un comportement indéfini classique. Vous avez promis func ne reviendrait pas, mais vous l'avez fait revenir quand même. Tu dois ramasser les morceaux quand ça casse.

Bien que le compilateur compile func de la manière habituelle (malgré votre noreturn ), le noreturn affecte les fonctions d'appel.

Vous pouvez le voir dans le listing de l'assemblage : le compilateur a supposé, en main que func ne reviendra pas. Par conséquent, il a littéralement supprimé tout le code après le call func (voir par vous-même à https://godbolt.org/g/8hW6ZR ). Le listing de l'assemblage n'est pas tronqué, il se termine littéralement après la balise call func parce que le compilateur suppose que tout code après cela serait inaccessible. Ainsi, lorsque func revient effectivement, main va commencer à exécuter n'importe quelle connerie qui suit le main fonction - qu'il s'agisse de rembourrage, de constantes immédiates ou d'une mer de 00 octets. Encore une fois - un comportement très peu défini.

C'est transitif - une fonction qui appelle une noreturn dans tous les chemins de code possibles peut, elle-même, être supposée être noreturn .

0 votes

Huh, c'est bizarre. noreturn semble désactiver l'optimisation du tailcall (ainsi que l'arrêt de l'inlining si la fonction noreturn appelle printf). Vous pouvez le constater même avec -O3 : godbolt.org/g/vFWp8E . Clang 4.0 et gcc 7.2 sont les mêmes ici.

0 votes

Ah, c'est parce que l'outil de gcc noreturn désactive spécifiquement l'optimisation de l'appel de queue pour une meilleure gestion de l'appel de queue. abort() backtraces : gcc.gnu.org/bugzilla/show_bug.cgi?id=55747#c4 .

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