Re: StdCall vs. CDecl

Tech-Archive recommends: Speed Up your PC by fixing your registry



Bob Altman wrote:
Because the worst thing that happens when you call a cdecl function as a stdcall function is that you leave the arguments on the stack after the function returns.

Thanks Jeroen, it's comforting to know that code that has seemed to work for a long time really should be expected to work. But...

I don't understand why "leaving arguments on the stack" is benign. The caller has his own data on the stack, which he expects to access relative to the stack pointer. If the caller expects the callee to "clean up the stack" (that is, restore the stack pointer to the value it had before the caller pushed a call frame onto the stack), then I would expect the caller to be thoroughly messed up after the call returns and the caller tries to get to its own data on the stack.

Yes and no. That's because the frame pointer will save the day. Just before returning, the callee will restore the caller's frame pointer, and in turn the caller will restore the stack pointer from that just before returning to its caller. By convention, local variables are addressed from the frame pointer, not the stack pointer, and the return address is just before the frame pointer, so everything works out.

That is to say, after the call, the stack pointer will be *wrong*, but unless the caller uses stack-based addressing, they will simply not notice. There's extraneous crud on the stack, but as long as this doesn't cause the stack to overflow (which could happen if you call the function in a loop), nothing happens. The net effect is as if the caller allocated extra local variables without being aware of it.

That's for calling a cdecl function as stdcall. It's much worse if you call a stdcall function as cdecl, because this will cause the stack pointer to be wrong in the other direction on returning. If the caller now calls more functions, it must push the function arguments onto the (wrong) stack. In doing so, it will likely corrupt its own local variables, and eventually the return address. Needless to say, not a pretty sight.

This is either not detected or silently fixed up, I don't know which one. My money's on the latter.

As I said above, I don't understand the "not detected" option. The "silently fixed up option" requires that the interop layer defends against this scenario by storing the old stack pointer away somewhere before the call so that it can detect that it must restore it after the call. I guess that's possible...

The interop layer simply counts on the frame pointer staying sane. It has no local variables and it doesn't call any other functions, so it doesn't care about a wrong stack pointer -- it will simply be overwritten as part of returning. So I was sort of wrong, in that it's *both* not detected *and* silently fixed up.

This is why it's perfectly safe to call a stdcall function as cdecl, or a cdecl function as stdcall from interop, as long as you get the number and size of arguments right. Disclaimer: it happens to be this way for 32-bit x86 in the current version of the framework. It is of course highly platform-specific, so using the right calling convention is still very much recommended.

On a related subject, is there an easy way to look at a DLL and figure out what calling convention it expects clients to use?

No, you just have to know (or disassemble the functions). That's why it's customary to use stdcall for exported DLL functions, just like the Win32 API does across the board.

So, why is StdCall the default if it's usually incorrect? (I'm going to guess that it's a COM thing.)

You may want to reread what I wrote. stdcall is the default, cdecl is the odd one out (and stdcall is the default for DLL imports, unless you're on Windows CE). If you're asking why cdecl is the default in the C compiler, or why it's *not* the default for exports: cdecl allows varargs, which are used in C, but stdcall is more efficient since the cleanup code only occurs once (in the called function).

COM, I'm happy to say, has absolutely nothing to do with this. But yes, in case you were wondering: all COM methods use stdcall too, with the special addition that they all pass a "this" pointer as their first argument.

--
J.
.



Relevant Pages

  • [PATCH 08/10] uml: cleanup run_helper() API to fix a leak
    ... Freeing the stack is left uselessly to the caller of run_helper in some cases - ... this is taken from run_helper_thread, but here it is useless, so no caller needs ... At this point passing a pointer is not needed - the stack pointer should be passed ...
    (Linux-Kernel)
  • Re: "character (len = :), allocatable :: foo" ?
    ... | calling conventions (stdcall vs whatever that other one was). ... OK, just FYI, here's a summary of stdcall vs. cdecl calling conventions. ... It's the duty of called routine to clean the stack. ...
    (comp.lang.fortran)
  • Re: calling convention stdcalll and cdecl call
    ... the compiler can somehow figure out how the function accesses its parameters, it can figure out how many there are, and pop the stack correctly without any additional information from the caller. ... int find{ ... This is similar to how it must take some action for a __stdcall member function, ...
    (microsoft.public.vc.language)
  • Re: How to export afunction in class?
    ... CONCURRENTLY with the fetch of the top of stack to the IP, so it all occurs in a SINGLE ... But if it wants stdcall, the cdecl will definitely screw the stack over. ... the calling convention required by Visual Basic clients to call ... C-interface DLL code. ...
    (microsoft.public.vc.mfc)
  • Re: calling convention stdcalll and cdecl call
    ... parameters from the stack. ... It uses cdecl convention, where the ... How would your hypothetical stdcall printf know to remove 12 bytes worth ...
    (microsoft.public.vc.language)