C ++后缀表达式未定义与未指定的行为

时间:2016-05-20 10:31:25

标签: c++ language-lawyer c++14 undefined-behavior operator-precedence

提前道歉,我知道评估订单的一般主题已经有很多SO问题。但是,看过它们之后,我想澄清一些我认为不等于重复的具体要点。假设我有以下代码:

#include <iostream>

auto myLambda(int& n)
{
    ++n;
    return [](int param) { std::cout << "param: " << param << std::endl; };
}

int main()
{
    int n{0};

    myLambda(n)(n);
    return 0;
}

上面的程序在编译时输出“n:0”。在这里我们有未指定的排序:它可以很容易地输出“n:1”发生了不同的评估顺序。

我的问题是:

  1. 在上面的最终函数调用(即lambda表达式调用)期间,后缀表达式myLambda(0),其参数n和后续函数之间的排序关系究竟是什么?函数调用本身?

  2. 以上是未定义未指定行为的示例 - 以及为何确切(参考标准)?

  3. 如果我将lambda代码更改为[](int param) { std::cout << "hello" << std::endl }(即使结果独立于其参数,因此任何评估顺序决策,使行为确定性),上述2)的答案是否仍然相同?

  4. 编辑:我已将lambda参数名称从'n'更改为'param',因为这似乎会造成混淆。

5 个答案:

答案 0 :(得分:17)

具有讽刺意味的是(由于该示例使用了C ++ 11的功能,而其他答案已被分散注意力),使此示例具有未指定行为的逻辑可追溯到C ++ 98,第5节,第4段

  

除非另有说明,否则单个运算符的操作数和单个表达式的子表达式的评估顺序以及副作用发生的顺序是未指定的。在前一个和下一个序列点之间,标量对象应通过表达式的计算最多修改其存储值一次。此外,只能访问先前值以确定要存储的值。对于完整表达式的子表达式的每个允许排序,应满足本段的要求;否则行为未定义。

基本上所有C ++标准中都存在相同的条款,但正如Marc van Leeuwen的评论中所指出的,最近的C ++标准不再使用序列点的概念。净效应是相同的:在一个陈述中,运算符的操作数和单个表达式的子表达式的顺序或评估仍未指定。

出现未指定的行为是因为在语句

中对表达式n进行了两次求值
myLambda(n)(n);

表达式n(获取引用)的一个评估与第一个(n)相关联,另一个表达式n的评估(以获取值)与第二个(n)。这两个表达式的评估顺序(即使它们是光学的,n都未指定)。

所有C ++标准中都存在类似的子句,并且在myLambda(n)(n)语句中具有相同的结果 - 未指定的行为,无论myLambda()如何实现

例如,myLambda()可以在C ++ 98(以及所有后来的C ++标准,包括C ++ 11及更高版本)中实现,就像这样

 class functor
 {
      functor() {};
      int operator()(int n) { std::cout << "n: " << n << std::endl; };
 };

 functor myLambda(int &n) 
 {
       ++n;
       return functor();
 }

 int main()
 {
      int n = 0;

      myLambda(n)(n);
      return 0;
 }

因为问题中的代码只是一种(C ++ 11)技术(或简写),以实现与此相同的效果。

以上回答了OP的问题1.和2.未指明的行为发生在main()中,与myLambda()本身的实现方式无关。

要回答OP的第三个问题,如果修改lambda(或我的示例中的functor operator())以不访问其参数的值,则行为仍未指定。唯一的区别是程序作为一个整体不会产生可见的输出,这可能会因编译器而异。

答案 1 :(得分:4)

lambda定义中的n是lambda定义的函数的形式参数。它与myLambda的参数没有任何联系,它也被称为n。这里的行为完全取决于调用这两个函数的方式。在myLambda(n)(n)中,未指定两个函数参数的评估顺序。可以使用0或1的参数调用lambda,具体取决于编译器。

如果已经使用[=n]()...定义了lambda,那么它的行为会有所不同。

答案 2 :(得分:2)

我没有设法找到对标准的正确引用,但我发现它与参数评估顺序here具有相似的行为,并且函数参数评估的顺序未由标准定义:

  

5.2.2函数调用

     

8 [注意:后缀表达式和参数表达式的评估都是相对于彼此无法排序的。参数表达式评估的所有副作用在函数之前排序   输入(见1.9)。 -end note ]

所以这里是关于不同编译器调用的内容:

g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out

GCC clang++ -std=c++14 main.cpp && ./a.out和MSVC

  

Int(int):0
  Int(const Int&amp;):0
  ++ n:0
  Int&amp;:0
  Int&amp;:0
  Int(const Int&amp;):0
  n:0
  ~Int():0
  ~Int():0
  ~Int():1

因此它创建变量并将其复制以传递给返回的lamba。

Clang struct A { A(int) { std::cout << "1" << std::endl; } ~A() { std::cout << "-1" << std::endl; } }; struct B { B(double) { std::cout << "2" << std::endl; } ~B() { std::cout << "-2" << std::endl; } }; void f(A, B) { } int main() { f(4, 5.); }

  

Int(int):0
  ++ n:0
  Int(const Int&amp;):1
  Int&amp;:1   Int&amp;:1   Int(const Int&amp;):1
  n:1
  ~Int():1
  ~Int():1
  ~Int():1

这里创建变量求值函数,然后passees复制lamba。

评估的顺序是:

choice = input('Nome Supereroe:')
while choice != 'Esc':
    printCharacterInfo(choice)
    choice = input('Nome Supereroe:')

def printCharacterInfo(character):
    try:
        print(super_heros[character])
    except KeyError:
        if character in hero_biography:
            proposeBiographyInformation(character)

        else:
            print('Nome inesistente')

def proposeBiographyInformation(name):
    if input('Desideri maggiori informazioni?') == 'Yes':
        print(hero_biography[name])

MSVC和GCC:

  

2
  1
  -1
  -2

锵:

  

1
  2
  -2
  -1

因为clang顺序是向前的,并且在评估函数的参数后传递lambda的参数

答案 3 :(得分:1)

行为是未指定的,因为在函数调用(myLambda(n))(n)中,后缀表达式(我给出了一对额外的冗余括号)相对于参数表达式n(最右边的一个)是无序的。但是没有未定义的行为,因为n的修改发生在函数调用myLambda(n)中。唯一明确的排序关系是,在实际调用lambda之前,评估myLambda(n)和最终n都显然是有序的。对于最后一个问题,lambda是选择完全忽略它的参数,然后行为不再是未指定的:虽然未指定哪个值作为参数传递,但是无论如何都没有可观察到的差异。

答案 4 :(得分:0)

评估子表达式的顺序是未指定的,除了运算符&amp;&amp;,||,?之外可能有所不同。和&#34;,&#34;。

编译器知道函数原型myLambda以及返回lambda(从返回类型中提取)。因为我的第一句话,编译器是免费的,他首先评估哪个表达式。这就是为什么你永远不应该在on表达式中调用具有额外副作用的函数。