Pourquoi cet extrait de code C ++ est-il compilé (la fonction non-vide ne renvoie pas de valeur)

Je l’ai trouvé dans une de mes bibliothèques ce matin:

static tvec4 Min(const tvec4& a, const tvec4& b, tvec4& out) { tvec3::Min(a,b,out); out.w = min(aw,bw); } 

Je m’attendrais à une erreur de compilation car cette méthode ne renvoie rien et le type de retour n’est pas void .

Les deux seules choses qui me viennent à l’esprit sont

  • Dans le seul endroit où cette méthode est appelée, la valeur de retour n’est ni utilisée ni stockée. (Cette méthode était supposée être void – le type de retour de tvec4 est une erreur de copier-coller)

  • un tvec4 construit par défaut est en cours de création, ce qui semble un peu différent, oh, tout le rest en C ++.

Je n’ai pas trouvé la partie de la spécification C ++ qui résout ce problème. Les références (ha) sont appréciées.

Mettre à jour

Dans certaines circonstances, cela génère une erreur dans VS2012. Je n’ai pas réduit les détails, mais c’est intéressant, néanmoins.

Il s’agit d’ un comportement indéfini de la section 6.6.3 projet de norme C ++ 11 .

[…] La fin d’une fonction équivaut à un retour sans valeur; Cela se traduit par un comportement indéfini dans une fonction de retour de valeur. […]

Cela signifie que le compilateur n’est pas obligé de fournir une erreur ni un avertissement, car il peut être difficile à diagnostiquer dans tous les cas. Nous pouvons voir cela à partir de la définition du comportement non défini dans le projet de norme à la section 1.3.24 qui dit:

[…] Le comportement indéfini admissible va de l’ignorance complète de la situation à des résultats imprévisibles, au comportement lors de la traduction ou à l’exécution du programme d’une manière documentée caractéristique de l’environnement (avec ou sans message de diagnostic), exécution (avec émission d’un message de diagnostic). […]

Bien que dans ce cas nous puissions obtenir les deux gcc et clang pour générer un wanring en utilisant le drapeau -Wall, ce qui me donne un avertissement similaire à ceci:

avertissement: le contrôle atteint la fin de la fonction non vide [-Wreturn-type]

Nous pouvons transformer cet avertissement particulier en erreur en utilisant l’ -Werror=return-type . J’aime aussi utiliser -Wextra -Wconversion -pedantic pour mes propres projets personnels.

Comme le mentionne ComicSansMS dans Visual Studio, ce code génère C4716, qui est une erreur par défaut. Le message que je vois est le suivant:

erreur C4716: ‘Min’: doit renvoyer une valeur

et dans le cas où tous les chemins de code ne renverraient pas une valeur, cela générerait C4715 , qui est un avertissement.

Peut-être quelques précisions sur le pourquoi de la question:

En réalité, il est assez difficile pour un compilateur C ++ de déterminer si une fonction existe sans valeur de retour. Outre les chemins de code qui se terminent par des instructions de retour explicites et ceux qui ne sont plus à la fin de la fonction, vous devez également tenir compte des longjmp ou longjmp dans la fonction elle-même, ainsi que dans toutes ses tâches.

Bien qu’il soit assez facile pour un compilateur d’identifier une fonction qui semble manquer un retour, il est beaucoup plus difficile de prouver qu’il manque un retour. Afin de lever les fournisseurs de compilateur de cette charge, la norme n’exige pas que cela génère une erreur.

Les éditeurs de compilateurs sont donc libres de générer un avertissement s’ils sont sûrs qu’une fonction manque un retour et que l’utilisateur est alors libre d’ignorer / masquer cet avertissement dans les rares cas où le compilateur était réellement faux.

†: Dans le cas général, cela équivaut au problème d’arrêt , il est donc impossible pour une machine de décider de manière fiable.

Comstackz votre code avec l’option -Wreturn-type :

 $ g++ -Wreturn-type source.cpp 

Cela vous avertira . Vous pouvez transformer l’avertissement en erreur si vous utilisez -Werror également:

 $ g++ -Wreturn-type -Werror source.cpp 

Notez que cela transformera tous les avertissements en erreurs. Donc, si vous voulez une erreur pour un avertissement spécifique, par exemple -Wreturn-type , tapez simplement type return-type sans -W partie comme:

 $ g++ -Werror=return-type source.cpp 

En général, vous devez toujours utiliser l’option -Wall qui inclut les avertissements les plus courants – cela inclut également une déclaration de retour manquante. Avec -Wall , vous pouvez utiliser -Wextra également, qui inclut d’autres avertissements non inclus par -Wall .

Peut-être quelques précisions sur la partie pourquoi de la question.

C ++ a été conçu pour qu’un très grand nombre de corps de code C préexistants soit compilé avec un minimum de modifications. Malheureusement, C lui-même payait une obligation similaire à celle du premier pré-standard C qui ne comportait même pas le mot-clé void et qui s’appuyait plutôt sur un type de retour par défaut int . Les fonctions C renvoyaient généralement des valeurs, et chaque fois qu’un code similaire à Algol / Pascal / Basic était écrit sans instructions de return , la fonction était, sous le capot, de renvoyer la mémoire qui restait sur la stack. Ni l’appelant ni l’appelé n’atsortingbuent la valeur des déchets de manière fiable. Si la corbeille est alors ignorée par chaque appelant, tout va bien et C ++ hérite de l’obligation morale de comstackr un tel code.

(Si la valeur renvoyée est utilisée par l’appelant, le code peut se comporter de manière non déterministe, similaire au traitement d’une variable non initialisée. La différence peut-elle être identifiée de manière fiable par un compilateur, dans une langue successeur hypothétique à C? L’appelant et l’appelé peuvent être dans des unités de compilation différentes.)

L’ int implicite n’est qu’une partie de l’inheritance C impliqué ici. Une fonction “répartiteur” peut, en fonction d’un paramètre, renvoyer une variété de types à partir de certaines twigs de code et ne renvoyer aucune valeur utile provenant d’autres twigs de code. Une telle fonction serait généralement déclarée pour renvoyer un type suffisamment long pour contenir l’un des types possibles et l’appelant pourrait avoir besoin de le lancer ou de l’extraire d’une union .

La cause la plus profonde est probablement la conviction des créateurs du langage C que les procédures qui ne renvoient aucune valeur ne sont qu’un cas particulier des fonctions qui ne le sont pas; Ce problème a été aggravé par le manque de concentration sur le type de sécurité des appels de fonction dans les dialectes C les plus anciens.

Bien que C ++ ait rompu la compatibilité avec certains des pires aspects de C ( exemple ), la volonté de comstackr une instruction de retour sans valeur (ou le retour implicite sans valeur à la fin d’une fonction) n’en faisait pas partie.

Comme déjà mentionné, il s’agit d’un comportement indéfini qui vous avertira du compilateur. La plupart des endroits où j’ai travaillé exigent que vous activiez les parameters du compilateur pour traiter les avertissements comme des erreurs – ce qui impose que tout votre code comstack avec 0 erreurs et 0 avertissements. C’est un bon exemple de pourquoi c’est une bonne idée.

Ceci est plus de la règle / fonctionnalité standard C ++ qui a tendance à être flexible avec les choses et qui tend à être plus proche de C.

Mais lorsque nous parlons des compilateurs, GCC ou VS, ils sont davantage destinés à un usage professionnel et à des objectives de développement variés, ce qui impose des règles de développement plus ssortingctes selon vos besoins.

Cela a du sens aussi, mon avis personnel, car le langage est tout au sujet des fonctionnalités et de leur utilisation alors que le compilateur définit les règles pour une utilisation optimale et optimale selon vos besoins.

Comme mentionné dans le post ci-dessus, le compilateur donne parfois l’erreur, donne parfois un avertissement et a la possibilité de sauter ces avertissements, etc., indiquant la liberté d’utiliser le langage et ses fonctionnalités d’une manière qui nous convient le mieux.

Parallèlement à cela, plusieurs autres questions mentionnent ce comportement de retour de résultat sans déclaration de return . Un exemple simple serait:

 int foo(int a, int b){ int c = a+b;} int main(){ int c = 5; int d = 5; printf("f(%d,%d) is %d\n", c, d, foo(c,d)); return 0; } 

Cette anomalie pourrait-elle être due aux propriétés de la stack et plus précisément:

Machines à adresse zéro

Dans les machines sans adresse, les emplacements des deux opérandes sont supposés être à un emplacement par défaut . Ces machines utilisent la stack comme source des opérandes d’entrée et le résultat retourne dans la stack. Stack est une structure de données LIFO (last-in-first-out) que tous les processeurs prennent en charge, qu’il s’agisse ou non de machines à adresse zéro. Comme son nom l’indique, le dernier élément placé sur la stack est le premier élément à sortir de la stack. Toutes les opérations sur ce type de machine supposent que les opérandes d’entrée requirejs sont les deux premières valeurs de la stack. Le résultat de l’opération est placé sur la stack.

De plus, pour accéder à la mémoire pour lire et écrire des données, les mêmes registres sont utilisés comme source de données et destination (registre DS (data segment)), qui stockent d’abord les variables nécessaires au calcul, puis le résultat renvoyé.

Remarque:

Avec cette réponse, je voudrais discuter d’une explication possible du comportement étrange au niveau de la machine (instruction), car il a déjà un contexte et son contenu est suffisamment large.