7

The title is really an accurate description of what I'm asking.

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

From what I've been testing, it doesn't seem to be __cdecl, __stdcall, __fastcall, and obviously is not __thiscall.

What is the convention and how does it function?

Thanks.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
Cryptography Man
  • 189
  • 2
  • 10
  • 3
    My psychic powers suggest the compiler is simply treating it as an inline function wherever it's used. Have you stepped through the assembly of where it's invoked to see how the parameters are passed on and off the stack? – selbie Feb 28 '19 at 06:05
  • 1
    extern "C" is not the same thing as __cdecl. It's actually got do with the type of *linker* symbols that get produced. C++ does name mangling (that's how overloading is done by intermixing type and function name together) ... however extern "C" tells the C++ compiler to *not perform namemangling* – Ahmed Masud Feb 28 '19 at 06:17
  • Why are you asking? These double-underscore abominations are only applicable to 32-bit x86 Windows. – n. m. could be an AI Feb 28 '19 at 06:22
  • There is no calling convention. It is dependent on the compiler and platform you are using. – Dunes Feb 28 '19 at 06:28

3 Answers3

7

Let's look at the generated assembly using the Debug build of a 32-bit Visual Studio project (default settings):

Here's my program:

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;
}

Where func1, func2, and func3 are defined in a separate source file to limit the possibility of automatic inlining.

Let's look at the generated assembly code for main:

    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  

For func1 and func3, it's the same signature. The argument is pushed onto the stack, the function call is invoked, and then the stack register (esp) is adjusted back (popped) to it's previous address - as expected for _cdecl calling convention. In __cdecl calling convention, the caller is responsible for restoring the stack pointer to its original address after a function call is made.

After the invocation of func2, there is no stack pointer adjustment. Consistent with __stdcall calling convention as it's declared. In __stdcall calling, the compiled function is responsible for popping the stack pointer back. Inspecting the assembly of func1 vs func2 shows that func1 ends with:

00211881  ret    // return, no stack adjustment

whereas func2 ends with this assembly:

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

Now before you conclude that "no linkage attribute" implies "__cdecl", keep in mind that Visual Studio projects have the following setting:

enter image description here

Let's change that Calling convention setting to __stdcall and see what the resulting assembly looks like:

    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  

Suddenly main isn't popping arguments after the invocation of func1 - hence func1 assumed the default calling convention of the project settings. And that's technically your answer.

There are environments where __stdcall being the default is the norm. Driver development for example...

selbie
  • 100,020
  • 15
  • 103
  • 173
6

All which extern "C" determines is the name mangling. Everything else is platform dependent.

I can only assume you are testing on x86-64 / win64 target?

If so, then all these call conventions simply no longer exist:
See https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=vs-2017 for Win64.
Search for "x86-64 System V ABI" for everything else.

All attempts of specifying the call convention are ignored, and a unified one is used.

For x86, it's again platform dependent what's default, so you are (used to be) better off specifying the call convention explicitly for platforms with multiple options.

Ext3h
  • 5,713
  • 17
  • 43
1

The wikibook on x86 Disassembly/Calling Conventions states this regarding the calling convention of extern "C" in C++:

Extern "C"
In a C++ source file, functions placed in an extern "C" block are guaranteed not to be mangled. This is done frequently when libraries are written in C++, and the functions need to be exported without being mangled. Even though the program is written in C++ and compiled with a C++ compiler, some of the functions might therefore not be mangled and will use one of the ordinary C calling conventions (typically CDECL).

And the ordinary C calling conventions are CDECL, STDCALL and FASTCALL.

P.W
  • 26,289
  • 6
  • 39
  • 76