juin
2007
Hier,
une question d’un forumeur pouvait se résumer ainsi (je change le texte et le code pour que ce soit plus simple, j’espère qu’il ne m’en voudra pas :p ) :
Pourquoi est-ce que ca marche ? J’appelle une fonction sur un pointeur nul comme voici
:
class A
{
public:
void F()
{
printf("Impossible ...");
}
};
int main()
{
A * a = NULL;
a->F();
return 0;
}
Et la console m’affiche bien ce qui est dans le printf ! Incroyable !
Et oui, c’est incoyable, mais comment l’expliquer ?
Nous appelons une méthode sur un objet qui vaut NULL, ca ne devrait pas marcher.
Ici l’explication est que vu que la fonction F() n’utilise pas this, la méthode se comporte comme une fonction globale classique sans paramètre implicite, donc le fait qu’il vaille null importe peu.
Pour le confirmer, il suffit de générer l’assembleur correspondant (propriétés, C/C++, fichier de sortie, sortie de l’assembleur : Assembly, code machine et source (/FAcs)).
Voilà la partie qui nous intéresse :
; 14 : a->F();
00000 68 00 00 00 00 push OFFSET ??_C@_0P@PLNHFHGC@Impossible?5?4?4?4?$AA@
00005 ff 15 00 00 00
00 call DWORD PTR __imp__printf
0000b 83 c4 04 add esp, 4
Ce qu’on peut voir ici, c’est un passage de paramètres (la chaine à afficher) avec push.
Un appel à la fonction printf
Et un déplacement du pointeur de pile (qui ne nous intéresse pas ici)
On comprend ici que le compilateur se moque complétement de l’objet courant puisque la méthode qu’il appelle ne fait qu’un appel à printf, donc que le pointeur d’objet soit NULL ou une valeur cohérente ca ne change rien.
Ce qui explique que cette construction peut fonctionner.
Mais que se passe-t-il dans le cas où l’on fait vraiment quelque chose avec notre objet ?
Prenons l’exemple suivant :
class A
{
public:
int i;
void F()
{
i = 1;
}
};
int main()
{
A * a = NULL;
a->F();
return 0;
}
et générons l’assembleur correspondant :
Voici ce qui nous intéresse :
00000 33 c0 xor eax, eax
; 15 : a->F();
00002 c7 00 01 00 00
00 mov DWORD PTR [eax], 1
On a donc une remise à zéro du registre eax (xor)
et on essai de mettre une valeur, 1 en l’occurence, dans l’adresse pointée par la valeur de eax, c’est à dire 0. Donc, on essai de mettre 1 dans l’adresse mémoire 0.
Et là, forcément, ca plante (plus précisément, on a un comportement indéterminé).
Pourquoi à la mémoire 0 ? parce que c’est la première place où sera stocké l’entier i à la zone mémoire pointée par l’objet. Si on avait eu plusieurs membres pour l’objet, ca aurait été différent.
Conclusion :
Si l’on appelle une méthode d’un objet NULL qui ne fait aucune utilisation de lui même (this), le code de la méthode sera exécuté.
Si l’objet implicite this est utilisé, alors ca va provoquer un comportement indéterminé
Edit : Notez que j’utilise Visual C++ 2005 avec les options d’optimisations par défaut. Cette fantaisie est dépendante du compilateur et de ses optimisations, comme me le fait remarquer très justement mon ami Thy. C’est pour ca qu’on a été regarder l’assembleur généré, pour comprendre un peu ce qu’il se passait.
Commentaires récents
- [Tests] Arrange Act Assert, une traduction ? dans
- [UnitTest][C#] Tester une méthode privée dans
- Récupérer une valeur d’un contrôle depuis une autre Form / inclusions croisées et déclaration anticipée dans
- Tutoriel : Utiliser la ListBox et l’Isolated Storage dans vos applications Windows Phone 7 avec Silverlight dans
- Tutoriel : Utiliser la ListBox et l’Isolated Storage dans vos applications Windows Phone 7 avec Silverlight dans
Archives
- janvier 2013
- avril 2012
- janvier 2012
- juin 2011
- janvier 2011
- décembre 2010
- novembre 2010
- septembre 2010
- juin 2010
- mars 2010
- février 2010
- janvier 2010
- décembre 2009
- novembre 2009
- octobre 2009
- septembre 2009
- août 2009
- juillet 2009
- mai 2009
- avril 2009
- mars 2009
- janvier 2009
- décembre 2008
- novembre 2008
- octobre 2008
- septembre 2008
- août 2008
- juillet 2008
- juin 2008
- mai 2008
- avril 2008
- mars 2008
- février 2008
- janvier 2008
- décembre 2007
- novembre 2007
- octobre 2007
- septembre 2007
- août 2007
- juillet 2007
- juin 2007
- mai 2007