calling convention and evaluation order

2020-06-21 05:10发布

问题:

I know that C++ doesn't specify the order in which parameters are passed to a function. But if we write the following code:

void __cdecl func(int a, int b, int c)
{
       printf("%d,%d,%d", a,b,c);
}
int main()
{
   int i=10;
   func(++i, i, ++i);
}

Can we reliably say the output would be 12,11,11 since the __cdecl ensures that argument-passing order is right to left?

回答1:

As per the Standard, there are two things you need to understand and differentiate:

  1. C++ doesn't specify the order in which parameters are passed to a function (as you said yourself, that is true!)

  2. C++ doesn't specify the order in which the function arguments are evaluated [expr.call].

Now, please note, __cdecl ensures only the first, not the second. Calling conventions decide only how the functions arguments will be passed, left-to-right or right-to-left; they can still be evaluated in ANY order!

Hope this clarifies your doubts regarding the calling conventions.

However, since these conventions are Microsoft compiler extension to C++, so your code is non-portable. In that case, you can see how MSVC++ compiler evaluates function arguments and be relax IF you don't want to run the same code on other platform!


func(++i, i, ++i);

Note that this particular code invokes undefined behavior, because i is incremented more than once without any intervening any sequence point.



回答2:

No, you cannot assume that.
An optimizing compiler will inline short functions. All that __stdcall will garantee is that the __stdcall version of function will be generated, but this does not mean that compiler cannot also inline it in the same time.
If you really want to be sure it's not inlined, you have to declare it in another compilation unit, alough linker optimizations could inline even in this case.

Also, the order of parameters on the stack have nothing to do with the order they are evaluated. For example, for a function call fn(a, b, c) GCC usually won't do

push c
push b
push a 
call fn

but rather

sub esp, 0xC  
mov [esp+8], c
mov [esp+4], b
mov [esp], a
call fn

Note that in the second case it has no restrictions on the order.



回答3:

It is possible for a particular C implementation to define what a compiler will do in certain cases which would, per the standard, be "undefined behavior". For example, setting an int variable to ~0U would constitute undefined behavior, but there's nothing in the C standard that wouldn't allow a compiler to evaluate the int as -1 (or -493, for that matter). Nor is there anything that would forbid a particular compiler vendor from stating that their particular compiler will in fact set the variable to -1. Since __cdecl isn't defined in the C standard, and is only applicable to certain compilers, the question of how its semantics are defined is up to those vendors; since the C standard lists it as undocumented behavior, it will only be documented to the extent particular vendors document it.



回答4:

You are changing the same variable more than once between sequence points (function argument evaluation is one sequence point), which cause undefined behaviour regardless of calling convention.