6
votes

Quelle est la convention d'appel pour extern "C" en C ++?

Le titre est vraiment une description précise de ce que je demande.

extern "C" int foo( int bar ) { return bar; }

D'après ce que j'ai testé, il ne semble pas être __cdecl code >, __stdcall , __fastcall , et n'est évidemment pas __thiscall.

Quelle est la convention et comment fonctionne-t-elle?

Merci .


4 commentaires

Mes pouvoirs psychiques suggèrent que le compilateur le traite simplement comme une fonction en ligne partout où il est utilisé. Avez-vous parcouru l'assemblage de l'endroit où il est appelé pour voir comment les paramètres sont transmis et sortis de la pile?


extern "C" n'est pas la même chose que __cdecl. C'est en fait lié au type de symboles linker qui sont produits. C ++ modifie les noms (c'est ainsi que la surcharge est effectuée en mélangeant le type et le nom de la fonction ensemble) ... cependant extern "C" dit au compilateur C ++ de ne pas effectuer le nommage


Pourquoi demandes-tu? Ces abominations à double trait de soulignement ne s'appliquent qu'à Windows x86 32 bits.


Il n'y a pas de convention d'appel. Cela dépend du compilateur et de la plate-forme que vous utilisez.


3 Réponses :


4
votes

Tout ce que extern "C" détermine, c'est le dénomination. Tout le reste dépend de la plate-forme.

Je ne peux que supposer que vous testez sur la cible x86-64 / win64?

Si tel est le cas, toutes ces conventions d'appel n'existent tout simplement plus:
Voir https://docs.microsoft .com / en-us / cpp / build / x64-call-convention? view = vs-2017 pour Win64.
Recherchez "x86-64 System V ABI" pour tout le reste.

Toutes les tentatives de spécification de la convention d'appel sont ignorées et une convention unifiée est utilisée.

Pour x86, la valeur par défaut dépend à nouveau de la plate-forme, il est donc préférable (avant) de spécifier explicitement la convention d'appel pour les plates-formes avec plusieurs options.


0 commentaires

1
votes

Le wikibook sur x86 Disassembly / Calling Conventions indique this concernant la convention d'appel de extern" C " dans C ++:

C externe
Dans un fichier source C ++, les fonctions placées dans un bloc externe "C" sont garanties de ne pas être mutilées. Cela se fait fréquemment lorsque les bibliothèques sont écrites en C ++ et que les fonctions doivent être exportées sans être mutilées. Même si le programme est écrit en C ++ et compilé avec un compilateur C ++, certaines des fonctions peuvent donc ne pas être mutilées et utiliseront l'une des conventions d'appel C ordinaires (généralement CDECL) .

Et les conventions d'appel C ordinaires sont CDECL, STDCALL et FASTCALL.


0 commentaires

4
votes

Examinons l'assembly généré en utilisant la version Debug d'un projet Visual Studio 32 bits (paramètres par défaut):

Voici mon programme:

    func1(1);
003417E8  push        1  
003417EA  call        _func1@4 (034120Dh)  
    func2(2);
003417EF  push        2  
003417F1  call        _func2@4 (0341131h)  
    func3(3);
003417F6  push        3  
003417F8  call        _func3 (034107Dh)  
003417FD  add         esp,4  

Où func1 , func2 et func3 sont définis dans un fichier source distinct pour limiter la possibilité d'inlining automatique.

Regardons au code d'assemblage généré pour main:

002118E1  ret         4   // return and pop 4 bytes from stack

Pour func1 et func3, c'est la même signature. L'argument est poussé sur la pile, l'appel de la fonction est invoqué, puis le registre de pile (esp) est ajusté (popped) à son adresse précédente - comme prévu pour la convention d'appel _cdecl . Dans la convention d'appel __cdecl, l'appelant est responsable de la restauration du pointeur de pile à son adresse d'origine après un appel de fonction.

Après l'invocation de func2 , il n'y a pas de pointeur de pile ajustement. Conforme à la convention d'appel de __stdcall telle qu'elle est déclarée. Dans l'appel de __stdcall, la fonction compilée est responsable de la récupération du pointeur de pile. L'inspection de l'assemblage de func1 vs func2 montre que func1 se termine par:

00211881  ret    // return, no stack adjustment

alors que func2 se termine par cet assembly:

    func1(1);
002117E8  push        1  
002117EA  call        _func1 (0211159h)  
002117EF  add         esp,4  
    func2(2);
002117F2  push        2  
002117F4  call        _func2@4 (0211131h)  
    func3(3);
002117F9  push        3  
002117FB  call        _func3 (021107Dh)  
00211800  add         esp,4  

Maintenant, avant de conclure que "aucun attribut de liaison" implique "__cdecl", gardez à l'esprit que les projets Visual Studio ont le paramètre suivant:

 entrez la description de l'image ici

Modifions ce paramètre de convention d'appel en __stdcall et voyons à quoi ressemble l'assembly résultant:

extern "C" int func1(int x);
extern "C" int __stdcall func2(int x);
extern "C" int __cdecl func3(int x);

int main()
{
    int x = 0;
    func1(1);
    func2(2);
    func3(2);
    return 0;
}

Soudainement main ne fait pas apparaître d'arguments après l'invocation de func1 - par conséquent, func1 a supposé la convention d'appel par défaut des paramètres du projet. Et c'est techniquement votre réponse.

Il existe des environnements où __stdcall étant la valeur par défaut est la norme. Développement de pilotes par exemple ...


1 commentaires

Réponse très informative .. Merci!