int __cdecl foobar(const char* pt_name, int anotherArg);
Ou même encore :
void __stdcall barfoo(void* ptr, int arg);
Les prototypes sont choisis au hasard. On ne sait pas ce que font ces fonctions à l'intérieur et encore moins à quoi elles servent.
D'après le titre de l'article, vous comprendrez que nous nous intéresserons aux notations __cdecl et __stdcall.
Tout d'abord, il faut savoir que ça n'est pas un souci majeur de savoir si on doit mettre telle ou telle directive. En effet, cela change peu de choses. Si l'on ne met aucune de ces deux directives - et c'est souvent le cas quand on écrit du C, parce qu'on en n'a strictement rien à cirer - alors ce sera la directive __cdecl qui sera utilisée.
Allez, j'en viens aux faits... Explications !
Comme vous pouvez le voir, on utilise ces directives lors de la définition de fonctions. En effet, pour ceux qui ne savent pas forcément comment se passe l'appel d'une fonction - d'un point de vue binaire, donc quand on exécute des instructions "natives" successivement - voici, sans entrer dans des explications barbares, comment ça se passe :
La routine appelante, qui peut aussi bien être votre fonction main ou même une autre fonction, va déposer les arguments de la fonction à appeler sur la pile d'exécution. Les informaticiens emploient couramment la traduction anglaise de ce terme : "execution stack", voire stack puisqu'ils sont fainéants. Cette pile d'exécution est très utile, et sert au programme pour mémoriser des données en général : variables, adresses mémoire, et j'en passe. Prenons le code suivant :
#include <stdio.h>
#include <stdlib.h>
void __cdecl doStuff(int, int);
void __stdcall doAnotherStuff(int, int, int, int);
int main(int argc, char **argv) {
doStuff(1, 2);
doAnotherStuff(1, 2, 3, 4);
return EXIT_SUCCESS;
}
void __cdecl doStuff(int arg1, int arg2) {
printf("I'm doStuff.\n");
}
void __stdcall doAnotherStuff(int foo1, int foo2, int foo3, int foo4) {
printf("- And I'm doAnotherStuff.\n");
}
Lors de l'appel à doStuff, le code présent dans la fonction main() va simplement "empiler" - ou "déposer sur la pile d'exécution" - les arguments 2 et 1. La directive __cdecl définit l'ordre de passage des arguments de la fonction de droite à gauche. C'est-à-dire que nous allons déposer le nombre 2 sur la pile, et 1 ensuite. Au final, notre pile ressemblera à ceci :
00000001
00000002
La valeur 2 se retrouve à la base de notre pile (bien qu'en réalité, on ait d'autres valeurs en-dessous mais elles ne nous intéressent pas) car elle a été empilée en premier. 1 se retrouve au sommet car il a été empilé en second. Vous pourrez penser que c'est bizarre, mais il n'en est rien ; il est plus aisé, pour la fonction appelée, de retrouver le premier argument au sommet de la pile !
La directive __stdcall utilise aussi cette convention, elle a ça en commun avec __cedcl. De ce fait, avant d'appeler la fonction doAnotherStuff, la pile d'exécution sera :
Arg1 = 00000001
Arg2 = 00000002
Arg3 = 00000003
Arg4 = 00000004
Pour information, il existe aussi des conventions où l'on empile les arguments de gauche à droite. Mais nous ne nous y intéresserons pas.
Vous suivez jusqu'ici ? Alors continuons.
Ne tournons plus autour du pot et expliquons les choses telles qu'elles sont réellement :
- Vous choisissez la directive __cdecl : une fois l'appel de la fonction déroulé, la pile sera intacte et ce sera logiquement à vous de la "nettoyer" ;
- Vous choisissez la directive __stdcall : une fois l'appel de la fonction déroulé, la pile sera dépourvue des arguments que nous avons empilés.
Que ce soient pour ceux qui ne comprennent pas nécessairement le fonctionnement exact de la pile - parce que j'ai été bref - ou ceux qui savent très très bien de quoi je parle, une question commune peut se poser : "Mais quel intérêt ? Pourquoi choisir l'une ou l'autre puisque ça ne change rien d'apparent ?"
Tout d'abord, sachez que si vous utilisez __stdcall, assurez-vous - et on ne sait jamais - d'avoir une copie de vos arguments quelque part en mémoire. Logiquement, vous en avez. Mais une erreur est si vite arrivée ! Un avantage notable serait évidemment des raisons de réduction de taille de pile : en effet, si votre fonction accepte plusieurs arguments voire une structure en entrée seulement - donc copiée intégralement sur la pile - alors la directive __stdcall peut s'avérer intéressante puisque la pile est dégagée au retour à la routine appelante.
Mais __cdecl a ses avantages aussi : c'est de garder la pile telle qu'on l'a trouvée avant l'appel ; pratique pour ceux qui utilisent une fonction sans forcément l'avoir programmée - l'API Win32, par exemple - et qui veulent que les choses restent à leur place. Ben, tenez, j'ai parlé de l'API Win32. Elles sont toutes, je pense, conventionnées par __stdcall.
Peut-être dis-je des choses fausses à propos des avantages / inconvénients ; le fainéant que je suis n'a pas pris la peine de se renseigner davantage sur ces directives-là. Donc ça peut se discuter, éventuellement. Et les commentaires sont là pour ça (je ne les ai jamais soumis à approbation et ça a excellemment marché jusque là).
Référence : http://msdn.microsoft.com/en-us/library/984x0h58%28v=VS.80%29.aspx
Geo



