【问题标题】:calling convention and evaluation order调用约定和评估顺序
【发布时间】:2011-05-23 01:30:46
【问题描述】:

我知道 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 确保参数传递顺序是从右到左?

【问题讨论】:

标签: c++ function arguments


【解决方案1】:

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

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

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

现在,请注意,__cdecl 只确保第一个,而不是第二个。 Calling conventions 决定如何传递函数参数,left-to-rightright-to-left仍然可以按任何顺序对其进行评估!

希望这能澄清您对调用约定的疑虑。

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


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

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

【讨论】:

  • 低级调用约定与表达式的求值顺序不同。
  • 它不仅不能在平台之间移植,而且不能在同一平台上的不同编译器之间移植,甚至不能在同一平台上同一编译器的不同版本之间移植,因为它是未定义的行为.
  • @Adam:我什至还要补充一点,对func 的两次后续调用也不能保证产生相同的订单。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    不,你不能假设。
    优化编译器将内联短函数。 __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
    

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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-06-05
        • 2013-06-28
        • 1970-01-01
        • 2012-02-22
        • 1970-01-01
        • 2010-11-24
        • 1970-01-01
        • 2015-10-22
        相关资源
        最近更新 更多