stdcall et cdecl

il existe (entre autres) deux types de conventions d'appel - stdcall et cdecl . J'ai quelques questions à leur sujet:

  1. quand une fonction cdecl est appelée, comment un appelant savoir si elle doit se libérer de la pile ? Sur le site de l'appel, est-ce que appelant savoir si la fonction appelée est un cdecl ou un stdcall la fonction ? Comment ça fonctionne ? Comment savoir à la cliente si elle doit gratuit haut de la pile ou pas ? Ou est c'est la responsabilité des linkers ?
  2. Si une fonction est déclarée comme stdcall appelle une fonction(qui a une convention d'appel comme cdecl), ou l'inverse, serait cette inapproprié ?
  3. en général, pouvons - nous dire que quel appel sera plus rapide-cdecl ou stdcall ?
72
demandé sur meJustAndrew 2010-08-04 13:51:51

9 réponses

Raymond Chen donne un bel aperçu de ce que __stdcall et __cdecl fait .

(1) L'appelant" sait " nettoyer la pile après avoir appelé une fonction parce que le compilateur connaît la convention d'appel de cette fonction et génère le code nécessaire.

void __stdcall StdcallFunc() {}

void __cdecl CdeclFunc()
{
    // The compiler knows that StdcallFunc() uses the __stdcall
    // convention at this point, so it generates the proper binary
    // for stack cleanup.
    StdcallFunc();
}

il est possible de ne pas respecter la Convention d'appel , comme ceci:

LRESULT MyWndProc(HWND hwnd, UINT msg,
    WPARAM wParam, LPARAM lParam);
// ...
// Compiler usually complains but there's this cast here...
windowClass.lpfnWndProc = reinterpret_cast<WNDPROC>(&MyWndProc);

So beaucoup d'échantillons de code se trompent. ce n'est même pas drôle. C'est censé être comme ceci:

// CALLBACK is #define'd as __stdcall
LRESULT CALLBACK MyWndProc(HWND hwnd, UINT msg
    WPARAM wParam, LPARAM lParam);
// ...
windowClass.lpfnWndProc = &MyWndProc;

Cependant, en supposant que le programmeur n'ignore pas les erreurs du compilateur, le compilateur générera le code nécessaire pour nettoyer la pile correctement puisqu'il connaîtra les conventions d'appel des fonctions impliquées.

(2) Les deux méthodes devraient fonctionner. En fait, cela se produit assez fréquemment au moins dans le code qui interagit avec L'API Windows, parce que __cdecl est la valeur par défaut pour les programmes C et C++ Selon le compilateur visuel c++ et les fonctions WinAPI utilisent la __stdcall convention .

(3) il ne devrait pas y avoir de différence réelle entre les deux.

67
répondu In silico 2015-07-12 11:38:03

dans les arguments CDECL sont poussés sur la pile dans l'ordre des retours, l'appelant efface la pile et le résultat est retourné via le registre du processeur (plus tard je l'appellerai"enregistrer A"). Dans STDCALL, il y a une différence, l'appelant ne nettoie pas la pile, l'appel le fait.

, Vous demandez lequel est le plus rapide. Nul. Vous devez utiliser native calling convention aussi longtemps que vous le pouvez. Changer la convention seulement s'il n'y a pas d'issue, lorsque vous utilisez des bibliothèques externes qui nécessitent certaines conventions à utiliser.

en outre, il y a d'autres conventions que le compilateur peut choisir par défaut une i.e. Visual C++ compiler utilise FASTCALL qui est théoriquement plus rapide en raison de l'utilisation plus étendue des registres de processeur.

habituellement, vous devez donner une signature de convention d'appel appropriée pour les fonctions de rappel passées à une bibliothèque externe, C.-à-d. que le rappel à qsort de la bibliothèque C doit être CDECL (si le compilateur par défaut utilise autre convention alors nous devons marquer la callback comme CDECL) ou diverses callbacks WinAPI doivent être STDCALL (WinAPI entier est STDCALL).

autre cas habituel peut être lorsque vous stockez des pointeurs vers certaines fonctions externes i.e. pour créer un pointeur pour la fonction WinAPI sa définition de type doit être marquée avec STDCALL.

Et ci-dessous est un exemple montrant comment le compilateur:

/* 1. calling function in C++ */
i = Function(x, y, z);

/* 2. function body in C++ */
int Function(int a, int b, int c) { return a + b + c; }

CDECL:

/* 1. calling CDECL 'Function' in pseudo-assembler (similar to what the compiler outputs) */
push on the stack a copy of 'z', then a copy of 'y', then a copy of 'x'
call (jump to function body, after function is finished it will jump back here, the address where to jump back is in registers)
move contents of register A to 'i' variable
pop all from the stack that we have pushed (copy of x, y and z)

/* 2. CDECL 'Function' body in pseudo-assembler */
/* Now copies of 'a', 'b' and 'c' variables are pushed onto the stack */
copy 'a' (from stack) to register A
copy 'b' (from stack) to register B
add A and B, store result in A
copy 'c' (from stack) to register B
add A and B, store result in A
jump back to caller code (a, b and c still on the stack, the result is in register A)

STDCALL:

/* 1. calling STDCALL in pseudo-assembler (similar to what the compiler outputs) */
push on the stack a copy of 'z', then a copy of 'y', then a copy of 'x'
call
move contents of register A to 'i' variable

/* 2. STDCALL 'Function' body in pseaudo-assembler */
pop 'a' from stack to register A
pop 'b' from stack to register B
add A and B, store result in A
pop 'c' from stack to register B
add A and B, store result in A
jump back to caller code (a, b and c are no more on the stack, result in register A)
36
répondu adf88 2018-01-27 04:27:11

j'ai remarqué un affichage qui dit qu'il n'a pas d'importance si vous appelez un __stdcall d'un __cdecl ou vice versa. Il n'.

la raison: avec __cdecl les arguments qui sont passés aux fonctions appelées sont retirés de la pile par la fonction d'appel, dans __stdcall , les arguments sont retirés de la pile par la fonction appelée. Si vous appelez un __cdecl fonction __stdcall , la pile n'est pas nettoyé à tous, donc finalement quand le __cdecl utilise une référence basée empilée pour les arguments ou l'adresse de retour utilisera les anciennes données au pointeur de pile courant. Si vous appelez une fonction __stdcall à partir d'une fonction __cdecl , la fonction __stdcall nettoie les arguments sur la pile, puis la fonction __cdecl le fait à nouveau, éventuellement en supprimant les informations de retour des fonctions d'appel.

Microsoft convention pour C essaie de contourner cela en mangling les noms. Un La fonction __cdecl est préfixée par un trait de soulignement. Un préfixe de fonction __stdcall avec un caractère de soulignement et un suffixe avec le signe " @ " et le nombre d'octets à supprimer. Eg __cdecl f (x) est lié en tant que _x , __stdcall f(int x) est lié en tant que _f@4sizeof(int) est de 4 octets)

si vous parvenez à passer le linker, profitez du mess de débogage.

12
répondu Lee Hamilton 2012-09-17 19:55:49

c'est spécifié dans le type de fonction. Quand vous avez un pointeur de fonction, il est supposé être cdecl sinon explicitement stdcall. Cela signifie que si vous avez un pointeur stdcall et un pointeur cdecl, vous ne pouvez pas les échanger. Les deux types de fonctions peuvent s'appeler l'un l'autre sans problème, il suffit d'obtenir un type quand vous attendez l'autre. Comme pour la vitesse, ils exercent les mêmes rôles, juste très légèrement différent, c'est vraiment hors de propos.

2
répondu Puppy 2010-08-04 09:59:43

je veux améliorer la réponse de @adf88. Je pense que le pseudo-code pour le STDCALL ne reflète pas la façon dont cela se passe dans la réalité. 'a', 'b', et ' c ' ne sont pas sortis de la pile dans le corps de fonction. Au lieu de cela, ils sont poussés par l'instruction ret ( ret 12 serait utilisé dans ce cas) que dans un swoop saute de nouveau à l'appelant et en même temps Pop 'a', 'b', et 'c' de la pile.

Voici ma version corrigée selon mon la compréhension:

STDCALL:

/* 1. calling STDCALL in pseudo-assembler (similar to what the compiler outputs) */
push on the stack a copy of 'z', then copy of 'y', then copy of 'x'
call
move contents of register A to 'i' variable

/* 2. STDCALL 'Function' body in pseaudo-assembler */ copy 'a' (from stack) to register A copy 'b' (from stack) to register B add A and B, store result in A copy 'c' (from stack) to register B add A and B, store result in A jump back to caller code and at the same time pop 'a', 'b' and 'c' off the stack (a, b and c are removed from the stack in this step, result in register A)

2
répondu golem 2015-10-25 01:42:19

L'appelant et de l'appelé doivent utiliser la même convention, au point de invokation - c'est la seule manière fiable. L'appelant et le destinataire de l'appel suivent un protocole prédéfini - par exemple, qui doit nettoyer la pile. Si les conventions ne correspondent pas, votre programme se heurte à un comportement non défini - il est probable qu'il s'écrase de manière spectaculaire.

ceci est seulement requis par site d'invocation - le code d'appel lui-même peut être une fonction avec n'importe quelle convention d'appel.

vous ne devriez pas remarquer de différence réelle dans la performance entre ces conventions. Si cela devient un problème, vous avez généralement besoin de faire moins d'appels - par exemple, changer l'algorithme.

1
répondu sharptooth 2010-08-04 09:59:06

ces choses sont spécifiques au compilateur et à la plate - forme. Ni le standard c++ ni le standard C++ne disent quoi que ce soit au sujet des conventions d'appel à l'exception de extern "C" en C++.

comment un appelant sait-il s'il doit libérer la pile ?

L'appelant sait la convention d'appel de la fonction et gère l'appel en conséquence.

Sur le site d'appel, l'appelant savoir si la fonction être appelé est une fonction cdecl ou stdcall ?

Oui.

comment ça marche ?

il fait partie de la déclaration de fonction.

comment l'appelant sait-il s'il doit libérer la pile ou non ?

l'appelant connaît les conventions d'appel et peut agir en conséquence.

ou est-ce la responsabilité des linkers ?

non, la Convention d'appel fait partie de la déclaration d'une fonction pour que le compilateur sache tout ce qu'il doit savoir.

Si une fonction est déclarée comme stdcall appelle une fonction(qui a une convention d'appel comme cdecl), ou l'inverse, cela serait-il inapproprié ?

Pas de. Pourquoi devrait-il?

In général, peut-on dire que l'appel sera plus rapide - cdecl ou stdcall ?

Je ne sais pas. Tester.

1
répondu sellibitze 2010-08-04 10:01:55

a) Lorsqu'une fonction cdecl est appelée par l'appelant, comment l'appelant sait-il s'il devrait libérer la pile?

le modificateur cdecl fait partie du prototype de fonction (ou du type pointeur de fonction, etc.) ainsi, l'appelant obtient l'information de là et agit en conséquence.

b) si une fonction déclarée comme stdcall appelle une fonction (qui a une convention d'appel comme cdecl), ou l'autre voie ronde, cela serait-il inapproprié?

Non, c'est bon.

c) en général, peut - on dire que quel appel sera le plus rapide-cdecl ou stdcall?

en général, je m'abstiendrai de telles déclarations. La distinction des questions par exemple. lorsque vous souhaitez utiliser va_arg fonctions. En théorie, il se pourrait que stdcall soit plus rapide et génère un code plus petit parce qu'il permet de combiner le disputes avec popping the locals, mais OTOH avec cdecl , vous pouvez faire la même chose, aussi, si vous êtes intelligent.

les conventions d'appel qui visent à être plus rapides font généralement du passage de registre.

0
répondu jpalecek 2010-08-04 10:05:46
Les conventions D'appel

n'ont rien à voir avec les langages de programmation C/C++ et sont plutôt spécifiques sur la façon dont un compilateur implémente le langage donné. Si vous utilisez toujours le même compilateur, vous n'avez pas à vous soucier d'appeler des conventions.

cependant, parfois nous voulons que le code binaire compilé par différents compilateurs fonctionne correctement. Lorsque nous le faisons, nous avons besoin de définir quelque chose appelé L'Interface binaire D'Application (ABI). L'ABI définit comment le compilateur convertit la source C/C++ en code machine. Cela inclut les conventions d'appel, la modification des noms et la disposition des tables en V. cdelc et stdcall sont deux conventions d'appel différentes couramment utilisées sur les plateformes x86.

en plaçant les informations sur la convention d'appel dans l'en-tête source, le compilateur saura quel code doit être généré pour interagir correctement avec l'exécutable donné.

-1
répondu doron 2010-08-04 10:16:31