这段代码是否定义明确?

Naw*_*waz 27 c++ operator-precedence sequence-points

此代码取自此处的讨论.

someInstance.Fun(++k).Gun(10).Sun(k).Tun();
Run Code Online (Sandbox Code Playgroud)

这段代码是否定义明确?是否++kkSun()之前评估过Fun ()?

如果k是用户定义的类型,而不是内置类型怎么办?以上函数调用顺序的方式与此不同:

eat(++k);drink(10);sleep(k);
Run Code Online (Sandbox Code Playgroud)

据我所知,在这两种情况下,每个函数调用后都存在一个序列点.如果是这样,为什么第一个案例也不能像第二个案例那样明确定义?

C++ ISO标准的1.9.17部分对序列点和功能评估进行了说明:

在调用函数时(无论函数是否为内联函数),在评估函数体中任何表达式或语句之前发生的所有函数参数(如果有) 之后,都会有 一个序列点.在复制返回值之后和执行函数外部的任何表达式之前,还有一个 序列点.

jal*_*alf 22

我想如果你确切地读到标准报价所说的内容,那么第一种情况就不会明确定义:

在调用函数时(无论函数是否为内联函数),在评估函数体中任何表达式或语句之前发生的所有函数参数(如果有)之后都有一个序列点

这告诉我们的不是"在函数的参数被评估之后唯一可能发生的事情就是实际的函数调用",而只是在参数的评估完成之后的某个时刻存在一个序列点,之前函数调用.

但如果你想象一个像这样的案例:

foo(X).bar(Y)
Run Code Online (Sandbox Code Playgroud)

这给我们的唯一保证是:

  • X在调用之前评估foo,和
  • Y在调用之前进行评估bar.

但是这样的订单仍然是可能的:

  1. 评估 X
  2. evalute Y
  3. (序列点Xfoo呼叫分离)
  4. 呼叫 foo
  5. (序列点Ybar呼叫分离)
  6. 呼叫 bar

当然,我们也可以交换前两个项目,Y之前进行评估X.为什么不?该标准仅要求在函数体的第一个语句之前完全评估函数的参数,并且上述序列满足该要求.

这是我的解释,至少.它似乎没有说在参数评估和函数体之间不会发生任何其他事情 - 只是那两个被序列点分开.

  • @Nawaz:也许,但请记住,如果标准没有另外说明,那么它是未定义的行为.如果我的解释是*可能*,如果我们找不到任何规则,那么它是"默认情况下"未定义的.如果代码定义明确,那么标准中必须存在1.9.17或其他地方的内容,这与我的解释相矛盾. (2认同)
  • @Nawaz:您似乎真的希望对此进行定义……您是否在工作中输了赌注?;-) (2认同)

Joh*_*itb 12

这取决于如何Sun定义.以下是明确定义的

struct A {
  A &Fun(int);
  A &Gun(int);
  A &Sun(int&);
  A &Tun();
};

void g() {
  A someInstance;
  int k = 0;
  someInstance.Fun(++k).Gun(10).Sun(k).Tun();
}
Run Code Online (Sandbox Code Playgroud)

如果更改参数类型Sunint,则它将变为未定义.让我们画一个版本的树int.

                     <eval body of Fun>
                             |
                             % // pre-call sequence point
                             | 
 { S(increment, k) }  <-  E(++x) 
                             |     
                      E(Fun(++k).Gun(10))
                             |
                      .------+-----.       .-- V(k)--%--<eval body of Sun>
                     /              \     /
                   E(Fun(++k).Gun(10).Sun(k))
                              |
                    .---------+---------. 
                   /                     \ 
                 E(Fun(++k).Gun(10).Sun(k).Tun())
                              |
                              % // full-expression sequence point
Run Code Online (Sandbox Code Playgroud)

可以看出,我们读取k(指定V(k))和k(在最顶部)没有被序列点分隔的副作用:在这个表达式中,相对于彼此的子表达式,没有序列点.最底部%表示全表达序列点.


Dav*_*har 10

这是未定义的行为,因为k的值在同一表达式中被修改和读取,没有插入序列点.看到这个问题的优秀长期答案.

1.9.17中的引用告诉您在调用函数体之前对所有函数参数进行求值,但没有说明在同一表达式中对不同函数调用的参数求值的相对顺序 - 没有保证"++ k Fun()在Sun()中的k之前进行评估".

eat(++k);drink(10);sleep(k);
Run Code Online (Sandbox Code Playgroud)

是不同的,因为它;是一个序列点,因此评估的顺序是明确定义的.


Ton*_*roy 8

作为一个小测试,考虑:

#include <iostream>

struct X
{
    const X& f(int n) const
    {
        std::cout << n << '\n';
        return *this;
    }
};

int main()
{
    int n = 1;

    X x;

    x.f(++n).f(++n).f(++n).f(++n);
}
Run Code Online (Sandbox Code Playgroud)

我使用gcc 3.4.6运行它并且没有优化并得到:

5
4
3
2
Run Code Online (Sandbox Code Playgroud)

......用-O3 ......

2
3
4
5
Run Code Online (Sandbox Code Playgroud)

因此,无论是3.4.6的版本都有一个主要的错误(这有点难以置信),或者正如Philip Potter建议的那样,序列是不确定的.(GCC 4.1.1有/无-O3产生5,5,5,5.)

编辑 - 我在以下评论中的讨论摘要:

  • 3.4.6确实可能有一个bug(嗯,是的)
  • 许多较新的编译器碰巧产生5/5/5/5 ......这是一个定义的行为吗?
    • 可能不是,因为它对应于在进行任何函数调用之前被"操作"的所有增量副作用,这不是标准所保证的任何人都建议的行为.
  • 这不是一个非常好的方法来调查标准的要求(特别是对于像3.4.6这样的旧编译器):同意,但它是一个有用的健全性检查

  • +1优秀的实证答案.我没想到找到不同的输出会很容易. (2认同)
  • @Tony:你为什么得到upvotes?:P这篇文章没有证实:D (2认同)
  • @Tony,@ Nawaz:获得5 5 5 5,1 2 3 4 5,5 4 3 2 1.所有与之一致的开始未定义. (2认同)

归档时间:

查看次数:

1758 次

最近记录:

15 年 前