2010-05-05 4 views
29

Certains vieux code que je viens de découvrir:Pourquoi ce code fonctionne-t-il toujours?

MLIST * new_mlist_link() 
{ 
    MLIST *new_link = (MLIST *) malloc(sizeof(MLIST)); 
    new_link->next = NULL; 
    new_link->mapi = NULL; 
    new_link->result = 0; 
} 

Cela a été appelé à construire une liste chaînée, mais je remarqué qu'il n'y a pas de déclaration:

return new_link; 

Même sans la déclaration de retour là-bas, la liste a encore été construite correctement. Pourquoi est-ce arrivé?

Edit: Plate-forme: Mandriva Linux 2009 64bit 2.6.24.7 serveur GCC 4.2.3-6mnb1

Edit: drôle ... ce code a également couru sur environ 5 succčs différentes installations de Linux, tout différentes versions/saveurs, ainsi qu'un Mac.

+2

Il n'y a pas de réponse possible en dehors de la chance stupide, à moins que (ou peut-être même si) vous nous faites savoir quelle plate-forme. – Potatoswatter

+1

Cela fait un bon cas pour l'utilisation d'un vérificateur de code statique. – semaj

+3

Intéressant qu'il a couru sur tant de plates-formes. Il n'a qu'une seule variable locale, donc il doit être assez standard pour GCC sur Intel de stocker une seule variable locale dans l'EAX (ou son équivalent 64 bits), et aussi d'utiliser ce registre pour les valeurs de retour. –

Répondre

35

Sur Windows 32 bits, la valeur de retour d'une fonction est la plupart du temps laissée dans le registre EAX. Des configurations similaires sont utilisées dans d'autres systèmes d'exploitation, bien que ce soit spécifique au compilateur. Cette fonction particulière stockait vraisemblablement la variable new_link dans le même emplacement, donc quand vous revenez sans retour, la variable dans cet emplacement a été traitée comme la valeur de retour par l'appelant.

Ceci est non-portable et très dangereux à faire réellement, mais c'est aussi une des petites choses qui rendent la programmation en C tellement amusante.

+4

+1, avec l'ajout que c'est l'ABI, pas le système d'exploitation qui définit la convention d'appel. – danben

+1

Cela explique pourquoi il a couru, mais pourquoi il est compilé est la question suivante. : P Vous penseriez que le compilateur vérifierait une déclaration de retour (je sais tout le mien faire). Quoi qu'il en soit, alors que cela fonctionne, il peut certainement être étiqueté comme une mauvaise chose. – ssube

+1

Je pense que ce style de codage était plus commun il y a 30 ans quand C devenait populaire. Le projet dont provient ce code a probablement été écrit par un codeur plus ancien (ou il y a longtemps) et avait depuis lors désactivé les avertissements du compilateur afin que cela soit compilé correctement. –

5

C'est essentiellement de la chance; Apparemment, le compilateur arrive à coller new_link dans le même endroit où il collerait une valeur retournée.

-1

Probablement une coïncidence:

L'espace pour la valeur de retour de la fonction est allouée à l'avance. Comme cette valeur n'est pas initialisée, elle aurait pu indiquer le même espace sur le tas que la mémoire allouée à la structure.

+1

Euh, non, les valeurs retournées sont généralement retournées dans les registres, ou, je suppose, sur la pile. Ce n'est pas un pointeur aléatoire qui est mystérieusement correct. Les valeurs de retour ne sont pas allouées. – WhirlWind

+1

Comme indiqué ci-dessus, les valeurs de retour sont renvoyées dans un registre si elles correspondent, comme ce serait le cas pour un pointeur. – Joel

+4

@Whirl: Sur certaines architectures, un espace est réservé sur la pile pour les valeurs de retour lorsqu'une procédure est appelée. Sur d'autres, le résultat est retourné dans un registre. Il diffère**. –

1

Cela fonctionne par coïncidence. Vous ne devriez pas compter sur cela.

7

Possible il suffit d'utiliser le registre EAX qui stocke normalement la valeur de retour de la dernière fonction qui a été appelée. Ce n'est pas une bonne pratique du tout! Le comportement pour ce genre de choses est indéfini .. Mais c'est cool de voir du travail ;-)

1

Le plus probable est que cela produira un bug très difficile à trouver. Je ne suis pas sûr d'où je l'ai lu, mais je me souviens que si vous oubliez de mettre une déclaration de retour, la plupart des compilateurs retourneront par défaut.

Voici un court exemple:

#include <iostream> 

using namespace std; 

int* getVal(); 

int main() 
{ 
     int *v = getVal(); 
     cout << "Value is: " << *v << endl; 
     return 0; 
} 

int* getVal() 
{ 
     // return nothing here 
} 

Pour moi, cela fonctionne aussi bien. Cependant, quand je l'exécute, j'obtiens une erreur de segment. Donc c'est vraiment indéfini. Juste parce qu'il compile, ne veut pas dire que ça va marcher.

+1

Watson je présume? –

0

Cela fonctionne parce que dans les années 1940, lorsque le langage C a été créé, il n'y avait pas de mot-clé return.Si vous regardez dans la section « fonctions » de la norme C43 Minsi, il a ceci à dire sur le sujet (entre autres):

16.4.3b For backwards compatibility the EAX register MUST be used to return 
     the address of the first chunk of memory allocated by malloc. 

</humour>

+4

Non, ce n'est pas le cas. Il n'y a pas de norme C43 MINSI, il fait juste ça. –

+0

Maintenant que nous connaissons tous l'humour de James. pourquoi down-voté? – tristan

5

Pour éviter ce problème, utilisez:

-Wreturn-type:

Warn whenever a function is defined with a return-type that defaults to int. Also warn about any return statement with no return-value in a function whose return-type is not void (falling off the end of the function body is considered returning without a value), and about a return statement with an expression in a function whose return-type is void.

-Werror=return-type pour activer le dessus dans une erreur:

Make the specified warning into an error. The specifier for a warning is appended, for example -Werror=switch turns the warnings controlled by -Wswitch into errors. This switch takes a negative form, to be used to negate -Werror for specific warnings, for example -Wno-error=switch makes -Wswitch warnings not be errors, even when -Werror is in effect. You can use the -fdiagnostics-show-option option to have each controllable warning amended with the option which controls it, to determine what to use with this option.

(de GCCs warning options)

Questions connexes