27

複合式の複数のオペランドが同じオブジェクトを変更する場合、それは未定義の動作であるとどこかで読んだことを漠然と覚えています。

このUBの例は以下のコードに示されていると思いますが、g ++、clang ++、およびVisual Studioでコンパイルしました。これらはすべて同じ値を出力し、異なるコンパイラーで予測できない値を生成することはできません。

#include <iostream>

int a( int& lhs ) { lhs -= 4; return lhs; }
int b( int& lhs ) { lhs *= 7; return lhs; }
int c( int& lhs ) { lhs += 1; return lhs; }
int d( int& lhs ) { lhs += 2; return lhs; }
int e( int& lhs ) { lhs *= 3; return lhs; }

int main( int argc, char **argv )
{
    int i = 100;
    int j = ( b( i ) + c( i ) ) * e( i ) / a( i ) * d( i );

    std::cout << i << ", " << j << std::endl;

    return 0;
}

この動作は未定義ですか、それとも実際には未定義ではないはずのUBの説明を思いついたのでしょうか。

誰かがこのUBの例を投稿してくれて、C++標準のどこにUBだと言っているのかを教えてくれたらありがたいです。

4

3 に答える 3

34

いいえそうではありません。未定義の動作はここでは問題外です(int算術がオーバーフローしないと仮定):のすべての変更はiシーケンスポイントによって分離されます(C ++ 03の用語を使用)。各関数の入口にはシーケンスポイントがあり、出口にはシーケンスポイントがあります。

動作はここでは指定されていません。

コードは実際には、未定義の動作と未指定の動作の違いを説明するためによく使用される古典的な例と同じパターンに従います。このことを考慮

int i = 1;
int j = ++i * ++i;

この例では、「結果は評価の順序に依存しないため、j常に6でなければならない」とよく言われます。動作が定義されていないため、これは無効な主張です。

ただし、この例では

int inc(int &i) { return ++i; }

int i = 1;
int j = inc(i) * inc(i);

動作は正式には指定されていません。つまり、評価の順序は指定されていません。ただし、式の結果は評価の順序にまったく依存しないため、j常に。になることが保証され6ます。これは、一般的に危険な不特定の動作の組み合わせが、完全に定義された結果につながる可能性がある方法の例です。

あなたの場合、あなたの表現の結果は評価の順序に決定的に依存します、それは結果が予測できないことを意味します。ただし、ここには未定義の動作はありません。つまり、プログラムがハードドライブをフォーマットすることは許可されていません。で予測できない結果を生成することのみが許可されていjます。

PS繰り返しになりますが、式の評価シナリオのいくつかは、符号付き整数のオーバーフロー(すべてを分析していません)につながり、それ自体が未定義の動作を引き起こすことが判明する場合があります。したがって、式に未定義の動作が発生する可能性があります。しかし、これはおそらくあなたの質問が何であるかではありません。

于 2012-12-22T17:52:19.903 に答える
12

いいえ、未定義の動作ではありません。

しかし、それは不特定の振る舞いを引き起こします。

これは、部分式が評価される順序が指定されていないためです。

int j = ( b( i ) + c( i ) ) * e( i ) / a( i ) * d( i );

上記の式では、サブ式は次のとおりです。

b(i)
c(i)
e(i)
a(i)
d(i)

任意の順序で評価できます。それらはすべて副作用があるため、結果はこの順序によって異なります。

式をすべてのサブ式に分割すると(これは擬似コードです)
、必要な順序を確認できます。上記の式は任意の順序で実行できるだけでなく、より高いレベルのサブ式とインターリーブできる可能性があります(いくつかの制約があります)。

tmp_1 = b(i)           // A
tmp_2 = c(i)           // B
tmp_3 = e(i)           // C
tmp_4 = a(i)           // D
tmp_5 = d(i)           // E

tmp_6 = tmp_1 + tmp_2  // F   (Happens after A and B)
tmp_7 = tmp_6 * tmp_3  // G   (Happens after C and F)
tmp_8 = tmp_7 / tmp_4  // H   (Happens after D and G)
tmp_9 = tmp_8 * tmp_5  // I   (Happens after E and H)

int j = tmp_9;         // J   (Happens after I)
于 2012-12-22T17:58:35.603 に答える
8

これは未定義の動作ではありませんが、結果は特定されていません。変更されたオブジェクトはi、関数に渡された参照のみです。ただし、関数の呼び出しにより、シーケンスポイントが導入されます(C ++ 2011はありません。そこでは別の名前で呼ばれます)。つまり、式内で複数の変更が行われ、未定義の動作が発生するという問題はありません。

ただし、式が評価される順序は指定されていません。その結果、評価の順序が変わると、異なる結果が得られる可能性があります。これは未定義の動作ではありません。結果は、考えられるすべての評価順序の1つです。未定義の動作とは、他のすべてのデータを破壊しながら、問題の式に対して「期待される」(プログラマーによって期待される)結果を生成するなど、プログラムが任意の方法で動作できることを意味します。

于 2012-12-22T17:59:21.650 に答える