27

このコードは、ここで行われている議論から取られています。

someInstance.Fun(++k).Gun(10).Sun(k).Tun();

このコードは明確に定義されていますか?++kFun()はSun()で以前に評価されてkいますか?

k組み込み型ではなく、ユーザー定義型の場合はどうなりますか?そして、上記の関数がorderを呼び出す方法は、これとどのように異なりますか。

eat(++k);drink(10);sleep(k);

私の知る限り、どちらの状況でも、各関数呼び出しの後にシーケンスポイントが存在します。もしそうなら、なぜ最初のケースも2番目のケースのように明確に定義できないのですか?

C ++ ISO標準のセクション1.9.17は、シーケンスポイントと関数評価について次のように述べています。

関数を呼び出すとき(関数がインラインであるかどうかに関係なく)、 すべての関数の引数(存在する場合) の評価後、関数本体の式またはステートメントの実行前に実行されるシーケンスポイントがあります。戻り値のコピー後、関数外の式の実行前にシーケンスポイントがあります。

4

6 に答える 6

22

その標準的な引用が言っていることを正確に読んだ場合、最初のケースは明確に定義されていないと思います。

関数を呼び出す場合(関数がインラインであるかどうかに関係なく)、すべての関数の引数(存在する場合)の評価後、関数本体の式またはステートメントの実行前に実行されるシーケンスポイントがあります。

これは、「関数の引数が評価された後に発生する可能性があるのは実際の関数呼び出しだけである」ということではなく、引数の評価が終了した後、およびその前のある時点にシーケンスポイントがあることを示しています。関数呼び出し。

しかし、このようなケースを想像すると、次のようになります。

foo(X).bar(Y)

これが私たちに与える唯一の保証は次のとおりです。

  • Xfoo、、およびを呼び出す前に評価されます。
  • Yを呼び出す前に評価されbarます。

しかし、このような注文はまだ可能です:

  1. 評価X
  2. 評価するY
  3. (呼び出しXから分離するシーケンスポイント)foo
  4. 電話foo
  5. (呼び出しYから分離するシーケンスポイント)bar
  6. 電話bar

Yもちろん、前に評価して、最初の2つの項目を入れ替えることもできXます。なぜだめですか?この標準では、関数の引数が関数本体の最初のステートメントの前に完全に評価されることのみが要求され、上記のシーケンスはその要件を満たしています。

少なくとも、それは私の解釈です。引数の評価と関数本体の間に他に何も起こらないとは言えないようです。これら2つがシーケンスポイントで区切られているだけです。

于 2011-01-17T03:51:19.663 に答える
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();
}

のパラメータタイプをに変更する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

kご覧のとおり、シーケンスポイントで区切られていない(で指定された)の読み取りと(最上部の)V(k)副作用があります。この式では、相互のサブ式に対して、kシーケンスポイント。一番下%は、完全な式のシーケンスポイントを示します。

于 2011-01-17T13:16:35.473 に答える
10

kの値は変更され、同じ式で読み取られ、シーケンスポイントが介在しないため、これは未定義の動作です。この質問に対する優れた長い答えを参照してください。

1.9.17からの引用は、関数の本体が呼び出される前にすべての関数の引数が評価されることを示していますが、同じ式内の異なる関数呼び出しに対する引数の評価の相対的な順序については何も述べていません-保証はありません「++kFun()はSun()のkの前に評価されます」。

eat(++k);drink(10);sleep(k);

;はシーケンスポイントであるため異なります。したがって、評価の順序は明確に定義されています。

于 2011-01-17T03:27:45.950 に答える
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);
}

私はこれをgcc3.4.6で実行し、最適化を行わずに次のようにします。

5
4
3
2

...-O3を使用...

2
3
4
5

したがって、そのバージョンの3.4.6に大きなバグがあるか(これは少し信じがたいです)、PhilipPotterが示唆したようにシーケンスが定義されていません。(-O3の有無にかかわらずGCC 4.1.1は5、5、5、5を生成しました。)

編集-以下のコメントでの議論の私の要約:

  • 3.4.6は本当にバグがあったかもしれません(まあ、はい)
  • 多くの新しいコンパイラはたまたま5/5/5/5を生成します...それは定義された動作ですか?
    • これは、関数呼び出しが行われる前にすべての増分副作用が「アクション」されることに対応しているため、おそらくそうではありません。これは、ここで誰もが標準によって保証できると示唆している動作ではありません。
  • これは、標準の要件を調査するための非常に優れたアプローチではありません(特に、3.4.6などの古いコンパイラを使用する場合):同意しましたが、有用な健全性チェックです
于 2011-01-17T03:37:19.880 に答える
1

コンパイラーの振る舞いは実際には何も証明できないことは知っていますが、コンパイラーの内部表現が何を与えるかをチェックするのは興味深いと思いました(アセンブリ検査よりも少し高いレベルです)。

私はこのコードでClang/LLVMオンラインデモを使用しました:

#include <stdio.h>
#include <stdlib.h>

struct X
{
  X const& f(int i) const
  {
    printf("%d\n", 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 onlineは警告しませんでした)


余談ですが、次の中間表現も生成されました(右にスクロール)。

@.str = private constant [4 x i8] c"%d\0A\00", 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.xxと同じように動作し、関数呼び出しを実行する前に最初にすべての引数を評価します。

于 2011-01-17T08:04:55.863 に答える
0

2番目のケースは確かに明確に定義されています。セミコロンで終わるトークンの文字列は、C++のアトミックステートメントです。各ステートメントは、次のステートメントが開始される前に解析、処理、および完了されます。

于 2011-01-17T03:28:30.467 に答える