调用函数中的这些评估在被调用函数体之前没有具体排序?

时间:2016-01-18 17:38:19

标签: c++ language-lawyer c++14

[intro.execution] / 15包含N4140第11页中的这些陈述(重点是我的):

  

调用函数时(无论函数是否为内联函数),每次都是   值计算和与任何参数相关的副作用   表达式,或使用指定被调用的后缀表达式   函数,在执行每个表达式之前排序或   被调用函数体中的语句。 [注意:价值   与不同论证相关的计算和副作用   表达式没有排序。 - 尾注]   调用函数中的每个评估(包括其他函数调用),在之前或之后没有特别排序   被调用函数体的执行是不确定的   关于被叫的执行顺序排序   功能 9

     

9)换句话说,函数执行不会与每个函数交错   其他

我想知道在执行被调用函数体之前,特定顺序的调用函数中的这些评估是什么?

*编辑* 我声称问题"sequence before" and "Every evaluation in the calling function" in C++中的两个答案与我的问题没有任何关系。请阅读我的问题和其中给出的答案。另请参阅我对@CoryKramer下面给出的答案的评论。

*编辑2 * 这可能就是我的问题的答案。请参阅DR 1949中建议的第3号决议:

调用函数中的每个评估(包括其他函数调用)都没有特别说明 在执行被调用函数体之前或之后对序列进行测序是不确定的 关于被调用函数的执行对于每个函数调用F,对于在F内发生的每个评估A以及在F内不发生但在同一线程上作为同一信号的一部分进行评估的每个评估B处理程序(如果有的话),在A之前对A进行测序,或者在A之前对A进行测序。 9 [注意:如果A和B不会被测序,那么它们将被不确定地排序。 - 后注]

2 个答案:

答案 0 :(得分:4)

如果您有两个返回int

的函数
int Foo();
int Bar();

然后你有一些调用函数

void SomeFunction(int x, int y);

然后调用代码看起来像

SomeFunction(Foo(), Bar());

他们说FooBar的执行顺序是不确定的。

答案 1 :(得分:2)

answer by CoryKramer完全正确,但可能没有充分阐述。 DR 1949中的澄清无关紧要。

关键是§1.9的第13段:它将“先前排序”的关系定义为部分顺序,并为两个评估A和B提供了四种可能性:

  1. A B

  2. 之前排序
  3. B在 A

  4. 之前排序
  5. (1)或(2)之一成立,但标准没有具体说明。在这种情况下,我们说A和B 不确定地排序

  6. (1)和(2)都不成立。在这种情况下,我们说A和B 未经过排序

  7. 不确定序列未序列之间存在差异,这是第15段中提到的这种差异。第15段从一般规则开始(强调添加) :

      

    除非另有说明,否则对单个运算符的操作数和单个表达式的子表达式的评估未经过排序

    其结果是在函数调用中

    f(argument1, argument2);
    

    评估argument1argument2 未经过排序。这使得以下未定义的行为

    f(i++, i++);
    

    但假设我们有:

    int incr(int& i) { return i++; }
    

    我们改为写道:

    f(incr(i), incr(i));
    

    如果我们应用了一般规则,那么这也将是未定义的行为,具有完全相同的参数:两个评估未被排序,因此两个函数调用的主体的评估未被排序,并且我们最终得到两个对同一变量的无序修改。

    但这确实不可取,因为它会导致混乱。 [应该指出的是,上面的例子被粗暴地简化了;这两个函数可能完全不同,并且可能没有命名公共变量。特别是,作为一种常见情况,这两个函数可能都将输出发送到std::cout,从而对同一个共享对象(std::cout本身)执行可变操作。]

    因此,对函数调用进行了显式异常:函数求值的主体总是按照包含函数调用的表达式的子表达式进行排序。所以在

    f(incr1(i), incr2(i));
    

    因为这些是函数调用,incr1incr2主体的评估是不确定地排序,而不是未排序< / em>,并且因为两个命令都导致i递增两次,所以在参数列表的评估结束时i的值是明确定义的。此外,传递给f的实际参数是未指定,而不是未定义;第一个或第二个会更大,但它们不会相等。

    该异常不适用于对调用本身的评估,仅适用于对被调用函数体的评估。所以f(g(i++), h(i++))仍然是未定义的行为,因为对两个子表达式i++的评估不是对任一函数体的评估的一部分。

    两个问题

    1. 第15段还将此异常扩展到函数调用,这些函数调用是语言语义的结果,包括运算符覆盖,结果很有趣

      f(i++, i++);
      
      如果i是具有operator++(int)覆盖的对象的实例,则

      未指定而不是未定义。类似地,

      f(std::cout << 'a', std::cout << 'b');
      

      会导致abba发送到std::cout(未指定哪个),但不是未定义的行为

    2. DR 1949的要点是“之后排序”从未正式定义过。因此,不是说“A在B之前或之后进行测序”,而是更精确的表述是“在B之前对A进行测序或在A之前对B进行测序”。通过正式定义“A在B之后排序”为“B在A之前排序”,您可以获得相同的逻辑效果。 DR 1949同时做到了。