【问题标题】:What are these evaluations in the calling function that are not specifically sequenced before the body of the called function?调用函数中的这些评估在被调用函数的主体之前没有具体排序是什么?
【发布时间】:2016-01-18 17:38:18
【问题描述】:

[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 在 B 之前排序,要么 B 在 A 之前排序。9 [注意:如果 A 和 B 不会被排序,那么它们是不确定的排序。 ——尾注]

【问题讨论】:

  • 信号处理程序:[intro.execution] 1.9\6; [intro.multithread] 1.10\23.2; [support.runtime] 18.10\9 - 不是吗?无论如何,您可能对this article 感兴趣。虽然我不是这里的专家......
  • @EugeneZavidovsky 你似乎走在了正确的轨道上。请参阅上面我的 ** 编辑 2 **。
  • 是的,但 CoryKramer 也是对的。有1.9\15。

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


【解决方案1】:

如果你有两个函数返回int

int Foo();
int Bar();

然后你有一些调用函数

void SomeFunction(int x, int y);

那么调用代码是这样的

SomeFunction(Foo(), Bar());

他们说Foo 和Bar 的执行顺序是不确定的。

【讨论】:

  • 你说的和上面[Note: Value computations and side effects associated with different argument expressions are unsequenced. —end note ]的注释有关。我只是看不出你所说的与我问题中突出显示的短语之间有任何关系。
  • @Belloc 听起来好像是指我提供的示例,或者可能是 Foo() + Bar() 之类的东西,其中这些函数执行的顺序再次未指定。
  • @Belloc 假设我们正在讨论Foo() 的执行。然后对Bar() 的调用是调用函数中的评估,在被调用函数的主体执行之前或之后没有特别排序。
【解决方案2】:

answer by CoryKramer 完全正确,但可能没有充分阐述。 DR 1949 中的说明不相关。

关键是第 1.9 节的第 13 段:它将关系“sequenced before”定义为偏序,并为两个评估 A 和 B 提供了四种可能性:

  1. A 在 B

    之前排序
  2. B 在 A

    之前排序
  3. (1) 或 (2) 中的一个成立,但标准没有指定哪个。在这种情况下,我们说 A 和 B 是不确定序列。

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

不确定排序和未排序之间存在差异,第 15 段中解决了这种差异。第 15 段从一般规则开始(强调添加) :

除非另有说明,对单个运算符的操作数和单个表达式的子表达式的求值是无序的。

这样的后果就是在函数调用中

f(argument1, argument2);

评估argument1 和argument2 是未排序的。这会导致以下未定义行为:

f(i++, i++);

但假设我们有:

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

我们改为写道:

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

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

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

因此,函数调用有一个明确的例外:函数评估的主体总是相对于包含函数调用的表达式的子表达式进行排序。所以在

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

因为这些是函数调用,所以对incr1 和incr2 的body 的评估是不确定排序的,而不是未排序的,并且由于这两个命令导致i 增加两次,所以在参数列表评估结束时i 的值是明确定义的。此外,传递给f 的实际参数是未指定,而不是未定义;第一个或第二个会更大,但它们不会相等。

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

两个方面

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

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

    是未指定而不是未定义。同样,

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

    将导致ab 或ba 被发送到std::cout(未指定哪个),但不是未定义的行为。

  2. DR 1949 的重点是“sequenced after”从未被正式定义过。因此,与其说“A 在 B 之前或之后排序”,更精确的表述是“A 在 B 之前排序,或者 B 在 A 之前排序”。您可以通过将“A 在 B 之后排序”正式定义为“B 在 A 之前排序”来实现相同的逻辑效果。 DR 1949 两者兼而有之。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-01-26
    • 1970-01-01
    • 2020-02-15
    • 1970-01-01
    • 2016-06-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多