【问题标题】:cout vs printf -- order of execution [duplicate]cout vs printf - 执行顺序[重复]
【发布时间】:2011-09-09 01:54:09
【问题描述】:

可能重复:
Compilers and argument order of evaluation in C++
cout << order of call to functions it prints?

  1. 这个:

    int k=3;
    printf("%d %d %d",k++,k,++k);
    

    输出为4 4 4,因为它们被推入堆栈:

    %d%d%d
    
    4  -- for k++    
    4  --for k    
    4  --for ++k
    

    对吗?

  2. 这个:

    int k = 3;
    cout << k++ << k << ++k;
    

    其实是重复的函数调用,所以相当于:

    ( ( (cout << k++) << k) << ++k);
    

    所以,我想首先k++ 然后k 然后++k 必须始终按此顺序执行,对吗?我相信函数调用是一个序列点,但输出因不同的实现而异。为什么会这样?

【问题讨论】:

    标签: c++ c


    【解决方案1】:

    参数的评估顺序在标准中未指定。这意味着,它可以按照实现想要的任何顺序发生。

    【讨论】:

    • 不,行为未定义。这比未指定的订单要糟糕得多。如果只是未指定该顺序,则该程序的可能输出数量将是有限的。但是由于k 在序列点之间被多次修改,UB 被调用,anything 可能发生。包括猴子的 ASCII 艺术被打印在标准输出上,或者你的所有文件都被删除。
    • 这是真的,先生。我应该澄清一下,在 printf 的情况下,未指定的行为会导致未定义的行为。相比之下,第二种情况是实现定义行为的示例,仅正如@Chris Lutz 在对其他答案的评论中所阐明的那样。
    • 我不确定 Chris 是否正确。见本杰明的评论...
    【解决方案2】:

    这是未定义的,因为 printf 语句中的 , 之间没有序列点。如果没有序列点,编译器可以随意对内存位置k 进行写入。

    现在您可能想知道“序列点到底是什么”以及它为什么相关?基本上,序列点是代码中存在问题的内存位置的点,在这种情况下,k 最多被修改一次。这里有更完整的描述:https://isocpp.org/wiki/faq/misc-technical-issues#double-mod-betw-seq-pt

    从FAQ中可以看出,printf中的,没有引入序列点。

    cout 的情况下,这是不同的,因为有3 个函数调用operator &gt;&gt;。函数调用引入了一个序列点,因此对内存位置k 的修改具有定义的顺序。然而(这是我错过的一点,但 Cubbi 指出)因为 C/C++ 没有定义函数参数的求值顺序,即使它们是函数,也可以按照编译器定义的任何顺序求值。所以在表达式中:

    f(h(), g())

    先计算 h() 还是 g() 未定义:http://www.stroustrup.com/bs_faq2.html#undefined。所以这就是为什么即使在 cout 的情况下,你也会从不同的编译器得到不同的结果,基本上是因为 cout &lt;&lt; k++ &lt;&lt; k &lt;&lt; ++k 转换为 cout.operator&lt;&lt;(k++).operator&lt;&lt;(k).operator(++k) 这实际上是一个像这样的表达式:f(h(g(cout, k++), k), ++k) 并且每个函数参数都在一个未指定的顺序。

    【讨论】:

    • 也可以免费提出SIGSEGV或更糟...
    【解决方案3】:

    您已经得到了涵盖对 printf 的调用的答案,但您也在问为什么 cout 语句的输出在编译器之间会有所不同。

    你说得对,相当于

    ( ( (cout<<k++)<<k)<<++k);
    

    现在,要计算该表达式并获得其结果,编译器必须计算最右边的&lt;&lt;。在对该&lt;&lt; 进行函数调用之前,必须计算其两个操作数( (cout&lt;&lt;k++)&lt;&lt;k)++k。并且这两个评估可以以任何顺序发生,甚至可以同时发生(编译器经常将来自两个独立(如编译器所认为的)代码分支的 cpu 指令交错。 而且由于这两个表达式的评估都涉及写入k,因此在 cout 的情况下行为也是未定义的。

    【讨论】:

    • 不,在 cout 的情况下它不是未定义的,因为这是 3 个函数调用,每个函数调用都会在它和下一个调用之间引入一个序列点。
    • @sashan 在评估f(a, b) 时,评估a 和评估b 是无序的,无论a 的内部子表达式如何在彼此之间进行排序。
    【解决方案4】:

    其实 printf 和 cout

    【讨论】:

    • 请注意,未指定和实现定义在 C++ 中是不同的。实现定义意味着实现应该选择一种方式,并且应该记录在案,而未指定意味着实现可以在相同的代码中为一种情况选择一种方式,并在下一种情况下选择另一种方式。
    • 定义了cout 版本,因为operator&lt;&lt; 接受两个参数,所以它被翻译成嵌套调用:operator&lt;&lt;(operator&lt;&lt;(operator&lt;&lt;(cout, k++), k), ++k);,它在每个之间创建序列点论据。这不是operator&lt;&lt;(cout, k++, k, ++k);(这是未指定的行为)。
    • @Chris:对operator&lt;&lt;的调用之间有序列点,但k++k++k之间没有序列点。
    • 确实,由于函数参数可以按任何顺序求值,那么在对operator&lt;&lt; 的外部调用中,++k 可以在对operator&lt;&lt; 的嵌套调用之前求值,或者可以求值然后。谁知道...?
    猜你喜欢
    • 2020-08-12
    • 2011-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-30
    • 1970-01-01
    • 2015-04-07
    • 1970-01-01
    相关资源
    最近更新 更多