Hi Asa,
To start with, we’ll begin with the origin of calling conventions…
Obviously, all compilers must have some sort of standard for how to pass
data from one function to another. For function foo() in foo.c to call a
function bar(int x, int y) in bar.c, the compiler needs to know where the
arguments are stored for foo() and how bar() is picking them up.
There are many different ways that we could achieve this, for instance we
could store bar()'s arguments in a static location, essentially making x
and y global variables. This, however, doesn’t work well with recursion, as
we’d need to find a way to store away the old values of x and y, etc. I
believe early versions of Fortran did have this form of argument passing,
but modern Fortran certainly doesn’t.
So, a much better way is to store values on the calling stack. This is
conventionally how all Algol-related languages work. C, Pascal,
Modula-[2,3].
One thing tho’, If the call is bar(1, 2), do we push 1 on the stack first,
then 2, or do we push 2 first, then 1? Well, there’s no real advantage of
one over the other, except for the fact that C has the “variable arguments”
issue. So if we have a function that takes variable arguments, it becomes
very hard to push the first argment first, because the callee will have no
way to determine where the first argument was called (unless of course we
pass some extra data to point to it, but that is ineffecient and
un-necessary if we can just turn it the other way around). So, the standard
C calling convention, by ease of implementing the “variable arguments”, is
to push the last argument first, so that the first argument is on top of
the stack when arriving in the called function.
So why doesn’t everyone use this? Well, the pascal compilers tend(ed) to
use the opposite calling convention, where the first argument is passed
first. Don’t ask me why… Maybe Mr Wirth will be able to explain…
To the compiler writer, it doesn’t make a whole lot of difference, as the
code to push the arguments would be implemented in a recursive function,
and we can implement both in one function, by recursing either before or
after the “push this argument”. Let’s assume that we’ve parsed and
translated the arguments into a linked list, and either cMode or pascalMode
is true depending on what order we want:
struct ArgList
{
struct ArgList *next;
…
}
void passArgs(struct ArgList *al)
{
if (al == NULL)
return;
if (pascalMode)
pushThisArg(al);
passArgs(al->next);
if (cMode)
pushThisArg(al);
}
There is no evidence that one of those are better than the other, aside
from C’s ability to handle variable arguments of course.
However, there is another ingredient here. Who’s responsible for cleaning
up the stack after the call: the callee or the caller? Since C supports
functions called with 1, 3, 17 or 465 arguments for the same function, the
only place where we know what the number of arguments to be removed from
the stack would be, is where the function was called. However, in a
function that we always know the number of arguments, we could just as well
remove the arguments in the callee function. There is an advantage to the
latter method, and that is that we reduce the size of the code, because we
don’t have to add an extra instruction at the end of every call to the
function to remove the arguments from the stack. In a large system, this
could easily make a big difference.
So, the origin of this is the fact that some people wanted to interface for
instnace Pascal or Fortran code with C.
Then we have optimisations. We can call functions with arguments passed in
registers. This saves the processor storing (pushing) the arguments on the
stack, and then picking them up from the stack again in the callee.
And finally, we may need to call C++ functions, and they have a “this”
pointer, which is the pointer to the actual object that the function is
tied to. Example:
class X
{
void foo(void);
}
main(…)
{
class X a, *p;
p = new X;
a.foo(); // “this = &a”
p->foo(); // “this = p”
}
The thiscall calling convention, which is used for all C++ function that
are not variable arguments or explicitly declared cdecl. This calling
convention puts this in ECX. On a C calling convention, “this” is pushed as
an extra “first argument” (i.e pushed last of all items).
stdcall is used as the interface to the OS, because it’s more
space-optimised than the “standard” C calling convention, because it
reduces the need for a “add esp, A” on every call to the Windows kernel.
With the rather large amount of kernel calls in a standard distribution of
Windows, this is probably a very good thing.
cdecl is used for standard applications, simply because it’s the “standard
C” calling convention. The main reason for this is that it works with “K&R
C”, as it doesn’t require prototypes to work correctly.Consider:
file1.c:
int bar(int x, int y, int z)
{
if (x == 1)
return 4;
if (x == 2)
return y * 4;
if (x == 3)
return y * z * 4;
}
file2.c:
int main(…)
{
int a, b, c;
a = bar(1);
b = bar(2, 6);
c = bar(3, 42, 18);
}
This is indeed valid C-code, but it requires C calling convention (and it
would probably cause all sorts of warnings in a ANSI compiler, but it
should compile and operate correctly).
We don’t really expect code that is using no prototypes in kernel calling
code, so not knowing the number of arguments isn’t really a problem here.
fastcall is one where we call with arguments in registers (which by the
way, may well be the standard calling convention in some processor
architectures, but x86 having very few registers is one of those where it’s
an “optional extra”). It should be a little bit faster, particularly if you
have many functions that take few arguments and don’t do much in the
function. It should for most cases also make shorter code.
The all-important rule is that both the caller and callee need to be aware
of the calling convention. The compiler/linker helps a little bit by
“decorating” the functions, so for instance a cdecl function “foo” will be
called “_foo”, whilst stdcall of “foo” may be “foo@4”. The @4 in this case
indicates that the function takes an argument of 4 bytes. This ensures that
we don’t link an old version of a function with a new prototyp that has
different number of arguments.
I’m sure I’ve missed some points here, but it’s a start…
–
Mats
xxxxx@lists.osr.com wrote on 11/02/2004 05:34:58 AM:
Just out of curiosity, why is there such a myriad of calling-
conventions, stdcall, cdecl,
fastcall, thiscall. what purpose does each one serve? what would be
the advantage of one
over the others? where did they come from? why is windows
exclusively stdcall? Lots of
questions I know, but it was just a few i wanted clarified in my head…
asa
Questions? First check the Kernel Driver FAQ at http://www.
osronline.com/article.cfm?id=256
You are currently subscribed to ntdev as: unknown lmsubst tag argument:
‘’
To unsubscribe send a blank email to xxxxx@lists.osr.com
ForwardSourceID:NT000067EA