This depends so much on the architecture that no one answer can tell you how to do this, and the advice to use <stdarg.h>
is overwhelmingly the proper way to solve the problem.
Even on a given machine, compiler options themselves can change the layout of the stack frame (not to mention the difference between 32-bit and 64-bit code generation).
However, if you're doing this simply because you're curious and are trying to investigate the architecture - a fine goal in my book - then the best way to noodle around with it is to write some code in C, compiler it to assembler, and look at the asm code to see how the parameters are passed.
// stack.c
#include <stdio.h>
int sum(int count, ...)
{
int sum = 0;
// stuff
return sum;
}
int main()
{
printf("%d\n", sum(1, 1));
printf("%d\n", sum(2, 1, 2));
printf("%d\n", sum(3, 1, 2, 3));
printf("%d\n", sum(4, 1, 2, 3, 4));
printf("%d\n", sum(10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
return 0;
}
On Linux, you can use either clang -S stack.c
or gcc -S stack.c
to produce a readable stack.s
assembler output file, and with Visual C on Windows, cl /Fa stack.c
will produce stack.asm
Now you can look at the different flavors of how the data is passed; when I try this on my Windows system, the 32-bit compiler does a routine push/push/push prior to the call (and might plausibly be negotiated on your own), while the 64-bit compiler does all kinds of things with registers that you won't manage yourself.
So, unless you're just curious about assembly language and architectures, I urge you to use <stdarg.h>
facilities to get this done.
EDIT responding to the OP's comment:
I declare __cdecl
as a suffix of each function, it should follow the cdecl calling convention, meaning the parameters will be passed into the stack from right to left, and also the caller has responsibility to clean the stack afterward.
This is a common implementation, but this is not what cdecl
means; it means "use the calling convention compatible with how C does things", and though passing arguments on the stack is one of them, it's not the only way.
- There is no requirement that arguments be passed left or right.
- There is no requirement that the stack grows down.
- There is no requirement that arguments be passed on the stack at all.
On my 64-bit system, the assembly language for the 10-parameter call looks like:
; Line 20
mov DWORD PTR [rsp+80], 10
mov DWORD PTR [rsp+72], 9
mov DWORD PTR [rsp+64], 8
mov DWORD PTR [rsp+56], 7
mov DWORD PTR [rsp+48], 6
mov DWORD PTR [rsp+40], 5
mov DWORD PTR [rsp+32], 4
mov r9d, 3
mov r8d, 2
mov edx, 1
mov ecx, 10
call sum
mov edx, eax
lea rcx, OFFSET FLAT:$SG8911
call printf
The first handful of parameters are passed right-to-left on the stack, but the rest are passed in registers.
What cdecl
really means is the caller has to clean up the parameters (however passed) after the function returns rather than the callee doing so. Callee-cleanup is a bit more efficient, but that does not support variable-length parameters.