【问题标题】:Function interleaving in pre C++17C++17 之前的函数交错
【发布时间】:2019-05-24 11:36:34
【问题描述】:

看看这个简单的函数调用:

f(a(), b());

根据标准,a()b()的调用顺序是未指定的。 C++17 有一个附加规则,不允许 a()b() 交错。据我所知,在 C++17 之前,没有这样的规则。

现在,看看这个简单的代码:

int v = 0;

int fn() {
    int t = v+1;
    v = t;
    return 0;
}

void foo(int, int) { }

int main() {
    foo(fn(), fn());
}

在 C++17 规则下,v 在调用foo 后肯定会有2 的值。但是,这让我想知道,对于 C++17 之前的版本,是否可以保证相同?还是v 最终变成1?如果我们只有v++ 而不是int t = v+1; v = t;,这有什么不同吗?

【问题讨论】:

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


【解决方案1】:

在以前的版本中也不允许函数调用交错。

引自 C++11 最终草案 (n3337)

1.9 程序执行[intro.execution]
...

15。 ... 调用函数时(无论该函数是否内联),与任何参数表达式或与指定被调用函数的后缀表达式相关的每个值计算和副作用都在执行主体中的每个表达式或语句之前进行排序称为函数。 [注意:与不同参数表达式相关的值计算和副作用是无序的。 ——尾注] 调用函数中的每个求值(包括其他函数调用)在被调用函数的主体执行之前或之后没有特别排序的,相对于被调用函数的执行是不确定的9 .


9) 换句话说,函数执行不会相互交错

在 C++14 版本的最终草案中也可以找到类似的措辞。

【讨论】:

  • OP 将函数与 表达式 混淆,这可能在 C++14 中交错。也就是说,如果你有一个像f(a() + b(), c() + d()) 这样的复合表达式,执行顺序可能是a(),然后是c(),然后是b(),然后是d(),等等。
【解决方案2】:

v 在所有 C++(和 C)版本中必须为 2。函数fn() 必须执行两次,显然每次执行它都会增加v。这里没有多线程,没有数据竞争,fn() 不可能只被部分执行然后被中断,而 fn() 的其他调用继续进行。

【讨论】:

    【解决方案3】:

    C++17 有一个附加规则,不允许 a() 和 b() 交错。据我所知,在 C++17 之前,没有这样的规则。

    这里有一些适用的规则,尽管措辞和一些细节变得更加准确。

    C++03 [intro.execution]/8:

    一旦函数开始执行,调用函数的表达式不会被计算,直到被调用函数的执行完成。 [脚注 8]

    [脚注 8]:换句话说,函数执行不会相互交错。

    尽管您可能会争辩说,这实际上并没有说明文本中从调用函数调用的其他函数,并且脚注不是正式的规范。

    C++11 改变了措辞,主要是因为它引入了多线程语义。 C++11 和 C++14 [intro.execution]/15,强调我的:

    调用函数时(无论函数是否内联),与任何参数表达式或指定被调用函数的后缀表达式相关的每个值计算和副作用,都在执行每个表达式或语句之前排序被调用函数的主体。 [注意: 与不同参数表达式相关的值计算和副作用是无序的。 - 结束说明] 调用函数(包括其他函数调用)中的每个评估,如果在被调用函数的主体执行之前或之后没有特别排序,则相对于被调用函数的执行。 [脚注 9]

    [脚注 9] 换句话说,函数执行不会相互交错。

    我认为这个措辞毫无疑问,至少在大多数情况下是这样。

    C++17 [intro.execution]/18:

    调用函数时(无论函数是否内联),与任何参数表达式或指定被调用函数的后缀表达式相关的每个值计算和副作用,都在执行每个表达式或语句之前排序被调用函数的主体。对于每个函数调用 F,对于在 F 内发生的每个评估 A 和每个不发生的评估 BF 内,但在同一线程上进行评估,并作为同一信号处理程序(如果有)的一部分,AB 之前排序或BA 之前排序。 [脚注 10] [注意: 如果 AB 不会被排序,那么它们是不确定排序的。 - 尾注]

    [脚注 10] 换句话说,函数执行不会相互交错。

    这是关于单独函数中的所有评估的更一般的陈述,而不仅仅是函数调用中的参数。但据我所知,这种更精确的措辞只是澄清了一些可能模棱两可的情况,但并没有真正改变语义行为。

    【讨论】:

      【解决方案4】:

      我怀疑我们混淆了两个概念。调用顺序仅在 c++17 中的表达式内固定。始终不允许在函数的 2 次调用之间交错函数中的语句,尽管当优化器获取您的代码时,只能保证效果。

      如果你写a() ; b(),那么 a() 将在 b 被完全执行之前被完全执行。如果你写 a(), b()a() && b() 也是一样

      如果你写a() + b(),那么在c++17之前不能保证a()会在b()之前执行,但是可以保证先执行的那个会在另一个之前完全完成执行。

      如果你写c(a(), b()) [我的理解] 再次不能保证 a() 将在 b() 之前执行,但是 /is/ 保证第一个执行函数的效果(无论哪个也就是说)将在第二个开始之前完成,当然可以保证 c() 在 a() 和 b() 都完成之前不会执行。

      优化器应该尊重该保证的意图,尽管它可能会采用 a() 和 b(),甚至 c() 的语句,并将它们混合在一起,运行代码应该与它们按顺序运行一样。 -O0 代码可以先执行 a() 然后 b(),而 -O3 代码可以执行 b() 然后 a() 吗?也许,虽然这会使调试变得非常困难,但我希望不会。

      编译器只需在最终结果中给出效果,并且在多线程中,另一个线程可以观察连续代码行乱序发生的效果,除非使用特定的线程感知构造。

      据我了解,优化器不能允许 a()、b() 和 c() 的特定效果在每个函数之间出现乱序,尽管在 c++17 之前的顺序a() 和 b() 的定义不明确 - b() 的所有效果都可能在 a() 的所有效果之前发生。

      【讨论】:

        猜你喜欢
        • 2021-09-10
        • 2020-01-03
        • 2017-12-24
        • 1970-01-01
        • 1970-01-01
        • 2014-03-23
        • 2021-04-17
        相关资源
        最近更新 更多