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.
There are three major calling conventions that are used with the C language on 32-bit x86 processors: STDCALL, CDECL, and FASTCALL. In addition, there is another calling convention typically used with C++: THISCALL.
__cdecl is the default calling convention for C and C++ programs. Because the stack is cleaned up by the caller, it can do vararg functions. The __cdecl calling convention creates larger executables than __stdcall, because it requires each function call to include stack cleanup code.
extern "C" specifies that the function is defined elsewhere and uses the C-language calling convention. The extern "C" modifier may also be applied to multiple function declarations in a block. In a template declaration, extern specifies that the template has already been instantiated elsewhere.
The __stdcall calling convention is used to call Win32 API functions. The callee cleans the stack, so the compiler makes vararg functions __cdecl . Functions that use this calling convention require a function prototype. The __stdcall modifier is Microsoft-specific.
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:
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...
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://docs.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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With