未定義の動作とシーケンスポイントがリロードされました


84

このトピックを次のトピックの続編と考えてください。

前の記事
未定義の動作とシーケンスポイント

この面白く複雑な表現をもう一度見てみましょう(イタリック体のフレーズは上記のトピック* smile *から取られています):

i += ++i;

これは未定義の振る舞いを引き起こすと言います。これを言うとき、私たちは暗黙のうちにタイプが組み込みタイプi1つであると仮定していると思います。

iユーザー定義型の場合はどうなりますか?そのタイプはIndex、この投稿の後半で定義されていると言います(以下を参照)。それでも未定義の振る舞いを呼び出しますか?

はいの場合、なぜですか?それは書くことと同等ではありませんi.operator+=(i.operator++());か、あるいは構文的に単純 i.add(i.inc());ですか?または、それらも未定義の振る舞いを呼び出しますか?

いいえの場合、なぜですか?結局のところ、オブジェクトiは連続するシーケンスポイント間で2回変更されます。経験則を思い出してください。式は、連続する「シーケンスポイント間でオブジェクトの値を1回だけ変更できます。また、i += ++iが式の場合 、undefined-behaviorを呼び出す必要があります。その場合、同等のものi.operator+=(i.operator++());i.add(i.inc());あり、undefined-behaviorも呼び出す必要があります。真実ではないようです!(私が理解している限り)

または、i += ++iそもそも表現ではないですか?もしそうなら、それは何ですか、そして表現の定義は何ですか?

それが式であると同時に、その動作明確に定義されている場合、式に関連付けられているシーケンスポイントの数は、に含まれるオペランドのタイプに何らかの形で依存することを意味します。私は(部分的にでも)正しいですか?


ちなみに、この表現はどうですか?

//Consider two cases:
//1. If a is an array of a built-in type
//2. If a is user-defined type which overloads the subscript operator!

a[++i] = i; //Taken from the previous topic. But here type of `i` is Index.

応答でもこれを考慮する必要があります(その動作を確実に知っている場合)。:-)


です

++++++i;

C ++ 03で明確に定義されていますか?結局のところ、これはこれです、

((i.operator++()).operator++()).operator++();

class Index
{
    int state;

    public:
        Index(int s) : state(s) {}
        Index& operator++()
        {
            state++;
            return *this;
        }
        Index& operator+=(const Index & index)
        {
            state+= index.state;
            return *this;
        }
        operator int()
        {
            return state;
        }
        Index & add(const Index & index)
        {
            state += index.state;
            return *this;
        }
        Index & inc()
        {
            state++;
            return *this;
        }
};

13
+1のすばらしい質問。これはすばらしい答えに影響を与えました。もっと読みやすくするためにリファクタリングする必要があるのはまだ恐ろしいコードだと言うべきだと思いますが、とにかくそれを知っているでしょう:)
Philip Potter

4
@質問は何ですか:誰がそれは同じだと言いましたか?または誰がそれは同じではないと言いましたか?それはあなたがそれらをどのように実装するかに依存しませんか?(注:の型sはユーザー定義型であると想定しています!)
Nawaz

5
2つのシーケンスポイント間でスカラーオブジェクトが2回変更されているのがわかりません...
ヨハネス・シャウブ- litb

3
@Johannes:それではスカラーオブジェクトについてです。それは何ですか?なんで聞いたことがないのかしら。たぶん、tutorials / C ++-faqがそれについて言及していないか、それを強調していないからですか?ビルトインタイプのオブジェクトとは違いますか?
ナワズ2011年

3
@Phillip:明らかに、私は実際にはそのようなコードを書くつもりはありません。実際、正気のプログラマーはそれを書くつもりはありません。これらの質問は通常、未定義の振る舞いとシーケンスポイントのビジネス全体をよりよく理解できるように考案されています。:-)
Nawaz

回答:


48

コードのように見えます

i.operator+=(i.operator ++());

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

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

これは、たとえば、 i.operator ++()パラメータとしてのoperator +=が評価後にシーケンスポイントを持っているます。つまり、オーバーロードされた演算子は関数であるため、通常の順序付けルールが適用されます。

ちなみに、すばらしい質問です。私は、あなたが私がすでに知っていると思っていた(そして私が知っていると思ったと思った)言語のすべてのニュアンスを理解するように強制している方法が本当に好きです。:-)



11

他の人が言ったように、あなたの i += ++iているように、関数を呼び出しているので例はユーザー定義型で機能し、関数はシーケンスポイントを構成します。

一方、a[++i] = iそれaが基本的な配列型であるか、ユーザー定義型であると仮定すると、それほど幸運ではありません。ここで発生する問題は、を含む式のどの部分がi最初に評価されるかがわからないことです。それ++iは評価され、operator[]そこでオブジェクトを取得するために(または生のバージョン)にi渡され、次にの値がそれに渡される可能性があります(これはiがインクリメントされた後です)。一方、おそらく後者の側が最初に評価され、後で割り当てられるように保存されてから、++iパーツが評価されます。


したがって、式の評価の順序が指定されていないため、結果はUBではなく指定されていませんか?
フィリップポッター

@Philip:unspecifiedは、コンパイラが動作を指定することを期待しているのに対し、undefinedはそのような義務を負わないことを意味します。コンパイラに最適化の余地を与えるために、ここでは定義されていないと思います。
Matthieu M.

@ノア:私も返信を投稿しました。ぜひチェックして、感想を聞かせてください。:-)
Nawaz

1
@Philip:5/4のルールにより、結果はUBになります。「この段落の要件は、完全な式の部分式の許容される順序ごとに満たされる必要があります。それ以外の場合、動作は定義されていません。」許可されるすべての順序に、変更++ii割り当てのRHSでの読み取りの間にシーケンスポイントがある場合、順序は指定されません。許容される順序の1つは、介在するシーケンスポイントなしで、これら2つのことを実行するため、動作は定義されていません。
スティーブジェソップ2011年

1
@Philip:未定義の動作を未定義の動作として定義しただけではありません。再び、場合不特定の行動の範囲が定義されていたいくつかを含む、次に全体的な動作は未定義です。場合は、不特定の行動の範囲は、すべての可能性で定義され、その後、全体的な動作は不定です。しかし、あなたは2番目の点で正しいです、私はユーザー定義のa組み込みを考えていましたi
スティーブジェソップ2011年

8

私はそれが明確に定義されていると思います:

C ++ドラフト標準(n1905)§1.9/ 16から:

「戻り値のコピー後、関数外の式の実行前にもシーケンスポイントがあります13)。C++のいくつかのコンテキストでは、対応する関数呼び出し構文が変換ユニットに表示されていなくても、関数呼び出しの評価が行われます。 [:新しい式の評価により、1つ以上の割り当て関数とコンストラクター関数が呼び出されます。5.3.4を参照してください。別の例として、関数呼び出し構文が表示されないコンテキストで変換関数(12.3.2)の呼び出しが発生する可能性があります。— end]関数の入口と出口のシーケンスポイント(上記のとおり)は、式の構文に関係なく、評価された関数呼び出しの機能です。関数を呼び出すは次のようになります。」

太字の部分に注意してください。これは、インクリメント関数呼び出し(i.operator ++())の後、複合代入呼び出し(i.operator+=)の前に実際にシーケンスポイントがあることを意味します。


6

了解しました。以前の回答を経て、私は自分自身の質問、特にノアだけが答えようとしたこの部分について再考しましたが、私は彼に完全には納得していません。

a[++i] = i;

ケース1:

もしはaビルトインタイプの配列です。それならノアが言ったことは正しい。あれは、

a [++ i] = aが基本的な配列型であると仮定すると、iはそれほど幸運ではありません。 またはユーザー定義のもの 。ここで発生する問題は、iを含む式のどの部分が最初に評価されるかがわからないことです。

したがってa[++i]=i、undefined-behaviorを呼び出すか、結果が指定されていません。それが何であれ、それは明確に定義されていません!

PS:上記の引用では、 取り消し線 もちろん私のものです。

ケース2:

場合は、aオーバーロードユーザー定義型のオブジェクトであるoperator[]、再び2つのケースがあります。

  1. オーバーロードされたoperator[]関数の戻り値の型が組み込み型の場合、a[++i]=iundefined-behaviorを再度呼び出すか、結果が指定されていません。
  2. しかし、オーバーロードされたoperator[]関数の戻り値の型がユーザー定義型であるa[++i] = i場合、この場合a[++i]=ia.operator[](++i).operator=(i);、と同じである書き込みと同等であるため、の動作は明確に定義されています(私が理解している限り)a[++i].operator=(i);。つまり、割り当てoperator=は、の返されたオブジェクトに対して呼び出されますa[++i]。これa[++i]は、戻るまで++iにすでに評価されているため、非常に明確に定義されているようです。次に、返されたオブジェクトはoperator=、の更新された値をi引数として渡す関数を呼び出します。これらの2つの呼び出しの間にはシーケンスポイントがあることに注意してください。また、構文により、これら2つの呼び出しの間に競合が発生しないことが保証されます。operator[]最初に呼び出され、続い++iて、渡された引数も最初に評価されます。

これはsomeInstance.Fun(++k).Gun(10).Sun(k).Tun();、連続する各関数呼び出しがユーザー定義型のオブジェクトを返すものと考えてください。私には、この状況は次のように見えeat(++k);drink(10);sleep(k)ます。どちらの状況でも、各関数呼び出しの後にシーケンスポイントが存在するためです。

私が間違っている場合は私を訂正してください。:-)


1
@Nawazでk++あり、シーケンスポイントで区切られkいません。それらは両方とも、どちらSunかの前に評価するか、評価することができFunます。言語は、の前に評価されることだけを要求し、の引数がのFunSunに評価されることは要求しません。参考にせず、また同じことを説明しているので、ここからは進めません。FunSun
フィリップポッター

1
@Nawaz:それらを分離するシーケンスポイントを定義するものがないため。Sun実行の前後にシーケンスポイントがありますが、Funの引数++kはその前後に評価される場合があります。Fun実行の前後にシーケンスポイントがありますが、Sunの引数kはその前後に評価される場合があります。したがって、一つの可能な場合は、両方のことであるkとは、++kいずれかの前に評価されSun又はFun評価され、そして両方に関数呼び出しシーケンスポイントの前であるので、分離ない配列点が存在しないkとは++k
フィリップポッター

1
@Philip:繰り返します:この状況はどのように違うのeat(i++);drink(10);sleep(i);ですか?...今でも、i++その前後に評価されるかもしれないと言えますか?
ナワズ2011年

1
@Nawaz:どうすれば自分自身をより明確にすることができますか?楽しい/日の例では、ありません何の間のシーケンスポイントkとは++k。食べる/飲む例で、との間にシーケンスポイントがiありi++ます。
フィリップポッター

3
@Philip:それはまったく意味がありません。Fun()とSun()の間にはシーケンスポイントが存在しますが、それらの引数の間にはシーケンスポイントは存在しません。と言うのと同じように、との間eat()sleep()はシーケンスポイントが存在しますが、引数の間には1つもありません。シーケンスポイントで区切られた2つの関数呼び出しへの引数は、どのようにして同じシーケンスポイントに属することができますか?
ナワズ2011年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.