调用约定和评估顺序

时间:2010-12-09 18:51:04

标签: c++ function arguments

我知道C ++没有指定参数传递给函数的顺序。但是如果我们编写以下代码:

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);
}

我们能否可靠地说输出为12,11,11,因为__cdecl确保参数传递顺序从右到左?

4 个答案:

答案 0 :(得分:13)

根据标准,您需要了解和区分两件事:

  1. C ++没有指定参数传递的顺序 功能(正如你自己所说,这是真的!)

  2. C ++没有指定函数参数评估的顺序 [expr.call]。

  3. 现在,请注意,__cdecl仅确保第一个,而不是第二个。 Calling conventions仅决定 函数参数的传递方式,left-to-rightright-to-left; 仍然可以按任意顺序评估它们!

    希望这可以澄清你对呼召惯例的疑虑。

    但是,由于这些约定是Microsoft编译器对C ++的扩展,因此您的代码是不可移植的。在这种情况下,您可以看到MSVC ++编译器如何评估函数参数并放松 IF 您不希望在其他平台上运行相同的代码!


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

    请注意,此特定代码会调用未定义的行为,因为i会多次递增而不会任何干扰任何序列点。

答案 1 :(得分:1)

不,你不能假设。
优化编译器将内联短函数。所有__stdcall都会保证会生成__stdcall版本的函数,但这并不意味着编译器也不能同时内联它。
如果你真的想确定它没有内联,你必须在另一个编译单元中声明它,即使在这种情况下,链接器优化也可以内联。

此外,堆栈上的参数顺序与它们的评估顺序无关。例如,对于函数调用fn(a, b, c),GCC通常不会执行

push c
push b
push a 
call fn

而是

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

请注意,在第二种情况下,它对订单没有限制。

答案 2 :(得分:1)

特定的C实现可以定义编译器在某些情况下将执行的操作,根据标准,这将是“未定义的行为”。例如,将int变量设置为~0U将构成未定义的行为,但C标准中没有任何内容不允许编译器将int计算为-1(或者-493,就此而言)。也没有任何禁止特定编译器供应商声明他们的特定编译器实际上将变量设置为-1的东西。由于__cdecl未在C标准中定义,并且仅适用于某些编译器,因此定义其语义的问题取决于这些供应商;由于C标准将其列为未记录的行为,因此只会记录特定供应商记录的行为。

答案 3 :(得分:0)

您在序列点之间多次更改同一个变量(函数参数求值是一个序列点),无论调用约定如何,都会导致未定义的行为。