C++のシーケンスポイントと副作用(side effect)
3244 ワード
i=(++i)+(i++)というものについての深い解釈は,単純で乱暴なundefined behaviorだけではない.
====
一.副作用(side effect)
式には2つの機能があります.各式には値(value)が生成され、副作用(side effect)が含まれる場合があります.副作用とは、いくつかの変数の値を変更することです.
次のようになります.
1:20//この式の値は20です.変数の値が変更されていないため、副作用はありません.
2:x=5//この式の値は5です.変数xの値を変更するため、副作用があります.
3:x=y+//この表現は、2つの変数の値が変更されたため、2つの副作用を示します.
4:x=x+//この式にも2つの副作用があります.変数xの値が2回変化したからです.
二.評価順序点
式評価ルールの核心は、シーケンスポイント[C 996.5 Expressions条項2][C++03 5 Expressions概要条項4]にある.
シーケンスポイントとは、一連のステップにおける「決済」のポイントを意味し、言語はこの時点の評価と副作用がすべて完了してこそ、次の部分に入ることができる.C/C++には、以下のような順序点しか存在しません.
1)セミコロン;
2)リロードされていないカンマ演算子の左オペランドに値を割り当てた後(すなわち','で)
3)再ロードされていない'|'演算子の左操作数が割り当てられた後(すなわち'|'で);
4)リロードされていない'&&'演算子の左操作数が割り当てられた後(&&)
5)三元演算子'?:'の左操作数に値を付けた後('?'所);
6)関数のすべてのパラメータが割り当てられた後、関数の最初の文が実行される前に.
7)関数の戻り値が呼び出し元にコピーされた後、その関数以外のコードが実行される前.
8)各ベースクラスとメンバーが初期化された後;
9)各完全な変数宣言には、int i,jなどの順序点がある.中カンマとセミコロンにはそれぞれ1つの順序点があります.
10)forサイクル制御条件の2つのセミコロンにはそれぞれ1つの順序点がある.
いずれの順序点についても、その前のすべての副作用はすでに完了しており、その後のすべての副作用はまだ発生していない.
2つの順序点の間で、サブエクスプレッションの評価と副作用の順序は同期されません.コードの結果が評価と副作用の発生順序に関連する場合、このようなコードには不確定な挙動がある(unspecified behavior).また、期間中に1つのビルドタイプに対して1回以上の書き込み操作を行うと、未定義の動作となる.
いずれの2つの順序点間の副作用の発生順序も未定義である.
次のようになります.
x=x++;
この式は1つの順序点のみであり、その順序点の前に2つの副作用があり、1つは自増、1つは賦値であり、この2つの副作用が発生する順序は未定義である.すなわち、自増演算と賦値演算のどちらが先に実行されるかは定義されていない(この順序は演算子の優先度とは無関係であることに注意し、演算子の優先度の意味を理解することに注意する).この実行順序はコンパイラメーカーに任せて自分で決めるので,異なるコンパイラに対して異なる結果が得られる可能性がある.
上記のコードについて:
gccコンパイラで実行した結果は11 4であった.
Visual Studio 2008で実行した結果は16 4
なぜなら
int i=0;
int m=(++i)+(++i)+(++i)+(++i);
2つのセミコロンの間には5つの副作用があり、この5つの副作用とサブ式の評価順序は定義されておらず、異なるコンパイラでは異なる結果が得られます.
また、その間にiに対して複数回の書き込み操作が行われ、これも未定義の行為であり、いかなる結果をもたらす可能性がある.
たとえば、
x[i]=i++;
printf("%d %d",i++,i++);
function(x,x++);
これらは未定義の動作です.
そのため、私たちは普段コードを書くとき、このようなスタイルの悪いコードをできるだけ書かないでください.それはプログラムに不確実性をもたらすだけでなく、プログラムの崩壊などの結果を引き起こす可能性があり、コードの移植性にとって致命的な打撃です.
例:
x[i]=i++;
このコードで代用できます.
x[i]=i;
i++;
function(x,x++);-> function(x,x);x=x+1;
このようなコードこそスタイルの良いコードです.
2つの隣接する順序点の間に同じ変数が2回以上変更されたり、読み取りと修正が同時に行われたりしてはならないことをできるだけ保証します.そうしないと、定義されていない動作が発生します.
====
一.副作用(side effect)
式には2つの機能があります.各式には値(value)が生成され、副作用(side effect)が含まれる場合があります.副作用とは、いくつかの変数の値を変更することです.
次のようになります.
1:20//この式の値は20です.変数の値が変更されていないため、副作用はありません.
2:x=5//この式の値は5です.変数xの値を変更するため、副作用があります.
3:x=y+//この表現は、2つの変数の値が変更されたため、2つの副作用を示します.
4:x=x+//この式にも2つの副作用があります.変数xの値が2回変化したからです.
二.評価順序点
式評価ルールの核心は、シーケンスポイント[C 996.5 Expressions条項2][C++03 5 Expressions概要条項4]にある.
シーケンスポイントとは、一連のステップにおける「決済」のポイントを意味し、言語はこの時点の評価と副作用がすべて完了してこそ、次の部分に入ることができる.C/C++には、以下のような順序点しか存在しません.
1)セミコロン;
2)リロードされていないカンマ演算子の左オペランドに値を割り当てた後(すなわち','で)
3)再ロードされていない'|'演算子の左操作数が割り当てられた後(すなわち'|'で);
4)リロードされていない'&&'演算子の左操作数が割り当てられた後(&&)
5)三元演算子'?:'の左操作数に値を付けた後('?'所);
6)関数のすべてのパラメータが割り当てられた後、関数の最初の文が実行される前に.
7)関数の戻り値が呼び出し元にコピーされた後、その関数以外のコードが実行される前.
8)各ベースクラスとメンバーが初期化された後;
9)各完全な変数宣言には、int i,jなどの順序点がある.中カンマとセミコロンにはそれぞれ1つの順序点があります.
10)forサイクル制御条件の2つのセミコロンにはそれぞれ1つの順序点がある.
いずれの順序点についても、その前のすべての副作用はすでに完了しており、その後のすべての副作用はまだ発生していない.
2つの順序点の間で、サブエクスプレッションの評価と副作用の順序は同期されません.コードの結果が評価と副作用の発生順序に関連する場合、このようなコードには不確定な挙動がある(unspecified behavior).また、期間中に1つのビルドタイプに対して1回以上の書き込み操作を行うと、未定義の動作となる.
いずれの2つの順序点間の副作用の発生順序も未定義である.
次のようになります.
x=x++;
この式は1つの順序点のみであり、その順序点の前に2つの副作用があり、1つは自増、1つは賦値であり、この2つの副作用が発生する順序は未定義である.すなわち、自増演算と賦値演算のどちらが先に実行されるかは定義されていない(この順序は演算子の優先度とは無関係であることに注意し、演算子の優先度の意味を理解することに注意する).この実行順序はコンパイラメーカーに任せて自分で決めるので,異なるコンパイラに対して異なる結果が得られる可能性がある.
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char*argv[])
{
int i=0;
int m=(++i)+(++i)+(++i)+(++i);
printf("%d %d
",m,i);
system("pause");
return0;
}
上記のコードについて:
gccコンパイラで実行した結果は11 4であった.
Visual Studio 2008で実行した結果は16 4
なぜなら
int i=0;
int m=(++i)+(++i)+(++i)+(++i);
2つのセミコロンの間には5つの副作用があり、この5つの副作用とサブ式の評価順序は定義されておらず、異なるコンパイラでは異なる結果が得られます.
また、その間にiに対して複数回の書き込み操作が行われ、これも未定義の行為であり、いかなる結果をもたらす可能性がある.
たとえば、
x[i]=i++;
printf("%d %d",i++,i++);
function(x,x++);
これらは未定義の動作です.
そのため、私たちは普段コードを書くとき、このようなスタイルの悪いコードをできるだけ書かないでください.それはプログラムに不確実性をもたらすだけでなく、プログラムの崩壊などの結果を引き起こす可能性があり、コードの移植性にとって致命的な打撃です.
例:
x[i]=i++;
このコードで代用できます.
x[i]=i;
i++;
function(x,x++);-> function(x,x);x=x+1;
このようなコードこそスタイルの良いコードです.
2つの隣接する順序点の間に同じ変数が2回以上変更されたり、読み取りと修正が同時に行われたりしてはならないことをできるだけ保証します.そうしないと、定義されていない動作が発生します.