Suppose I have a function:
int sumN(int n, ...)
{
int sum = 0;
va_list vl;
va_start(vl, n);
for (int i = 0; i < n; i++)
sum += va_arg(vl, int);
va_end(vl);
return sum;
}
Called as sumN(3, 10, 20, 30);
The function is cdecl
, which means caller cleanup. So, what happens is something like:
; Push arguments right-to-left
push 30
push 20
push 10
push 3
call sumN
add esp, 16 ; Remove arguments from stack (equivalent to 4 pops)
For regular functions that take a fixed number of arguments, the callee can perform the cleanup, as part of the ret
instruction (e.g. ret 16
). That doesn't work here because the callee can't know how many arguments were pushed - I could call it as sumN(1, 10, 20, 30, 40, 50);
and cause a stack corruption.
Now, I want to do it anyway. Maybe I have a tool that parses the source code before the build and makes sure all calls are legitimate. And I'm calling sumN()
50k times in my codebase, so the extra size from the last instruction adds up.
For the above implementation, it's easily done in assembly, but if it were a printf
function or something where the logic to figure out the size is a bit more complex, that's no longer an option. Still, I could do some inline assembly or something and fix the implementation of sumN
to pop the stack. But if anyone has a better solution, that's very welcome.
The big question, however, is how to tell the compiler that the function is callee cleanup when it has ...
in its declaration? How to prevent the compiler from generating the add esp, 16
instruction?
Ideally I need this for msvc, gcc and clang, but msvc is a priority.