这段代码是否定义明确?

此代码取自此处的讨论。
someInstance.Fun(++k).Gun(10).Sun(k).Tun();
这段代码是否定义明确? Fun()中的
++k
是否在Sun()中的
k
之前进行了评估? 如果
k
是用户定义的类型,而不是内置类型怎么办?以上函数调用顺序的方式与此不同:
eat(++k);drink(10);sleep(k);
据我所知,在这两种情况下,每个函数调用后都存在一个序列点。如果是这样,为什么第一个案例也不能像第二个案例那样明确定义? C ++ ISO标准的1.9.17部分对序列点和功能评估进行了说明:   调用函数时(无论是否为   不是函数是内联的),有   评估后的序列点   所有函数参数(如果有)   这是在执行之前发生的   中的任何表达或陈述   功能体。还有一个   复制后的序列点   返回值和之前   在外面执行任何表达式   功能。     
已邀请:
这取决于如何定义
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();
}
如果将参数类型
Sun
更改为
int
,则它将变为未定义。让我们画出一张
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
可以看出,我们读取了
k
(由
V(k)
表示)和
k
(在最顶部)的副作用,它们没有被序列点分开:在这个表达式中,相对于彼此的子表达式,根本没有序列点。最底部
%
表示全表达序列点。     
我想如果你确切地读到标准报价所说的内容,那么第一种情况就不会明确定义:   在调用函数时(无论函数是否为内联函数),在评估函数体中任何表达式或语句之前发生的所有函数参数(如果有)之后都有一个序列点 这告诉我们的不是“在函数的参数被评估之后唯一可能发生的事情就是实际的函数调用”,而只是在参数的评估结束之后的某个时刻有一个序列点,之前函数调用。 但如果你想象一个像这样的案例:
foo(X).bar(Y)
这给我们的唯一保证是:
X
在拨打
foo
之前进行评估,并且
Y
在拨打
bar
之前进行评估。 但是这样的订单仍然是可能的: 评估
X
评估
Y
(序列点从
foo
调用分离
X
) 叫
foo
(序列点从
bar
调用分离
Y
) 叫
bar
当然,我们也可以调换前两项,在
X
之前评估
Y
。为什么不?该标准仅要求在函数体的第一个语句之前完全评估函数的参数,并且上述序列满足该要求。 这是我的解释,至少。它似乎没有说在参数评估和函数体之间不会发生任何其他事情 - 只是那两个被序列点分开。     
这是未定义的行为,因为k的值在同一表达式中被修改和读取,没有插入序列点。看到这个问题的优秀长期答案。 1.9.17中的引用告诉您在调用函数体之前对所有函数参数进行求值,但没有说明在同一表达式中对不同函数调用的参数求值的相对顺序 - 没有保证“++ k Fun()在Sun()中的k之前进行评估”。
eat(++k);drink(10);sleep(k);
是不同的,因为
;
是一个序列点,因此评估的顺序是明确定义的。     
作为一个小测试,考虑:
#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);
}
我使用gcc 3.4.6运行它并且没有优化并得到:
5
4
3
2
......用-O3 ......
2
3
4
5
因此,无论是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这样的旧编译器):同意,但它是一个有用的健全性检查     
我知道编译器的行为无法真正证明什么,但我认为检查编译器的内部表示会给出什么(仍然比程序集检查更高一级)会很有趣。 我在这段代码中使用了Clang / LLVM在线演示:
#include <stdio.h>
#include <stdlib.h>

struct X
{
  X const& f(int i) const
  {
    printf("%dn", i);
    return *this;
  }
};

int main(int argc, char **argv) {
  int i = 0;
  X x;
  x.f(++i).f(++i).f(++i);         // line 16
}
并使用标准优化(在C ++模式下)编译,它给出:   /tmp/webcompile/_13371_0.cc:在函数'int main(int,char **)'中:   /tmp/webcompile/_13371_0.cc:16:警告:'i'上的操作可能未定义 我觉得有趣(其他任何编译器都警告过这个吗?Comeau在线没有) 另外,它还产生了以下中间表示(向右滚动):
@.str = private constant [4 x i8] c"%dA0", align 1 ; <[4 x i8]*> [#uses=1]

define i32 @main(i32 %argc, i8** nocapture %argv) nounwind {
entry:
  %0 = tail call i32 (i8*, ...)* @printf(i8* noalias getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 3) nounwind ; <i32> [#uses=0]
                                                                                                             ^^^^^
  %1 = tail call i32 (i8*, ...)* @printf(i8* noalias getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 3) nounwind ; <i32> [#uses=0]
                                                                                                             ^^^^^
  %2 = tail call i32 (i8*, ...)* @printf(i8* noalias getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 3) nounwind ; <i32> [#uses=0]
                                                                                                             ^^^^^
  ret i32 0
}
显然,Clang的行为类似于gcc 4.x.x,并且在执行任何函数调用之前首先计算所有参数。     
第二种情况肯定是明确的。以分号结尾的一串标记是C ++中的原子语句。在下一个语句开始之前,将对每个语句进行解析,处理和完成。     

要回复问题请先登录注册