web-dev-qa-db-fra.com

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

J'ai trouvé cela 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(a.w,b.w);
}

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

  • Au seul endroit où cette méthode est appelée, la valeur de retour n'est pas utilisée ou stockée. (Cette méthode était censée être void - le tvec4 le type de retour est une erreur de copier-coller)

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

Je n'ai pas trouvé la partie de la spécification C++ qui traite de cela. Les références (ha) sont appréciées.

Mise à jour

Dans certaines circonstances , cela génère une erreur dans VS2012. Je n'ai pas précisé les détails, mais c'est quand même intéressant.

129
3Dave

Il s'agit de comportement indéfini du projet de norme C++ 11 section 6.6.3 La déclaration de retour paragraphe 2 qui dit:

[...] La sortie d'une fin de 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 généralement parce qu'il peut être difficile à diagnostiquer dans tous les cas. Nous pouvons le voir dans la définition de comportement indéfini dans le projet de norme à la section 1.3.24 qui dit:

[...] Les comportements indéfinis autorisés vont de l'ignorance complète de la situation avec des résultats imprévisibles, au comportement pendant la traduction ou l'exécution du programme d'une manière documentée caractéristique de l'environnement (avec ou sans émission d'un message de diagnostic), à la fin d'une traduction ou exécution (avec émission d'un message de diagnostic). [...]

Bien que dans ce cas, nous pouvons obtenir à la fois gcc et clang pour générer un wanring en utilisant le -Wall flag, qui me donne un avertissement similaire à celui-ci:

avertissement: la commande atteint la fin de la fonction non nulle [-Type de retour]

Nous pouvons transformer cet avertissement particulier en une erreur en utilisant le -Werror=return-type drapeau. J'aime aussi utiliser -Wextra -Wconversion -pedantic pour mes projets personnels.

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

erreur C4716: 'Min': doit retourner une valeur

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

154
Shafik Yaghmour

Peut-être quelques explications sur la partie pourquoi de la question:

En fait, il est en fait assez difficile † pour un compilateur C++ de déterminer si une fonction se termine sans valeur de retour. Outre les chemins de code qui se terminent par des instructions de retour explicites et ceux qui tombent à la fin de la fonction, vous devez également prendre en compte les levées d'exceptions potentielles ou longjmps dans la fonction elle-même, ainsi que toutes ses callees.

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'elle est manquante un retour. Afin de lever les fournisseurs de compilateurs de cette charge, la norme n'exige pas que cela génère une erreur.

Les vendeurs de compilateurs sont donc libres de générer un avertissement s'ils sont tout à fait sûrs qu'une fonction manque un retour et l'utilisateur est alors libre d'ignorer/masquer cet avertissement dans les rares cas où le compilateur était en fait faux.

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

58
ComicSansMS

Compilez votre code avec -Wreturn-type option:

$ g++ -Wreturn-type source.cpp

Cela vous donnera avertissement. Vous pouvez transformer l'avertissement en erreur si vous utilisez -Werror aussi:

$ 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, dites -Wreturn-type, tapez simplement return-type sans pour autant -W en tant que:

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

En général, vous devez toujours utiliser -Wall option qui inclut les avertissements les plus courants - cela inclut également la déclaration de retour manquante. De même que -Wall, vous pouvez utiliser -Wextra également, qui inclut d'autres avertissements non inclus par -Wall.

22
Nawaz

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

C++ a été conçu de sorte qu'un très grand corps de corps préexistant de code C se compile avec un minimum de changements. Malheureusement, C lui-même payait un droit similaire au premier C pré-standard qui n'avait même pas le mot clé void et s'appuyait à la place sur un type de retour par défaut int. Les fonctions C renvoyaient généralement des valeurs, et chaque fois que du code superficiellement similaire aux procédures ALGOL/Pascal/Basic était écrit sans aucune instruction return, la fonction renvoyait, sous le capot, les ordures restantes sur la pile. Ni l'appelant ni l'appelé n'attribue la valeur des déchets de manière fiable. Si les ordures sont ensuite ignorées par chaque appelant, tout va bien et C++ hérite de l'obligation morale de compiler 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, semblable au traitement d'une variable non initialisée. La différence pourrait-elle être identifiée de manière fiable par un compilateur, dans un langage de successeur hypothétique en C? Ce n'est guère possible. L'appelant et l'appelé peuvent se trouver dans des unités de compilation différentes.)

L'implicite int n'est qu'une partie de l'héritage C impliqué ici. Une fonction "dispatcher" peut, selon un paramètre, renvoyer une variété de types de certaines branches de code et ne retourner aucune valeur utile à partir d'autres branches de code. Une telle fonction serait généralement déclarée pour renvoyer un type assez longtemps pour contenir l'un des types possibles et l'appelant pourrait avoir besoin de le convertir ou de l'extraire d'un union.

Donc, la cause la plus profonde est probablement la croyance des créateurs du langage C que les procédures qui ne retournent aucune valeur ne sont qu'un cas spécial sans importance des fonctions qui le font; ce problème a été aggravé par le manque de concentration sur la sécurité des types d'appels de fonction dans les dialectes C les plus anciens.

Alors que C++ cassait la compatibilité avec certains des pires aspects de C ( exemple ), la volonté de compiler une déclaration de retour sans valeur (ou le retour implicite sans valeur à la fin d'une fonction) était pas l'un d'eux.

21
Jirka Hanika

Comme déjà mentionné, ce comportement n'est pas défini et vous donnera un avertissement du compilateur. La plupart des endroits où j'ai travaillé vous demandent d'activer les paramètres du compilateur pour traiter les avertissements comme des erreurs - ce qui oblige tout votre code à compiler avec 0 erreurs et 0 avertissements. Ceci est un bon exemple de la raison pour laquelle c'est une bonne idée.

12
Zac Howland

Il s'agit davantage de la règle/fonctionnalité C++ standard qui tend à être flexible avec les choses et qui tend à être plus proche de C.

Mais lorsque nous parlons des compilateurs, GCC ou VS, ils sont plus à usage professionnel et à des fins de développement variées et imposent donc des règles de développement plus strictes selon vos besoins.

Cela a également du sens, à mon avis, car le langage est tout au sujet des fonctionnalités et de son utilisation tandis 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 également la possibilité de sauter ces avertissements, etc., indiquant la liberté d'utiliser le langage et ses fonctionnalités de la manière qui nous convient le mieux.

2
Bhupesh Pant

Parallèlement à cela, plusieurs autres questions mentionnent ce comportement de renvoi d'un résultat sans avoir une instruction 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 pile et plus précisément:

Machines à adresse zéro

Dans les machines à adresse zéro, les emplacements de les deux opérandes sont supposés être à un emplacement par défaut. Ces machines utilisent la pile comme source des opérandes d'entrée et le résultat retourne dans la pile. La pile est une structure de données LIFO (dernier entré, premier sorti) prise en charge par tous les processeurs, qu'ils soient ou non des machines à adresse zéro. Comme son nom l'indique, le dernier élément placé sur le pile est le premier élément à être retiré de la pile. Toutes les opérations sur ce type de machine supposent que les opérandes d'entrée requis sont les deux premières valeurs de la pile. Le résultat de l'opération est placé au-dessus de la pile.

En plus de cela, pour accéder à la mémoire pour lire et écrire des données, les mêmes registres sont utilisés comme source et destination de données (registre DS (segment de données)), 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 est couvert dans une gamme suffisamment large.

0
Ziezi