C〓〓の実行の順序の持ってくるいくつかの潜在的な問題に関して
前言
プログラムを作成する時、人々の直感的な感覚は、プログラムの実行順序は文の順序によって行われると思われます。しかし、多くのプログラミング言語の仕様は、実際の実行順序とステートメントの作成順序が一致しないことを可能にする。実際、コンパイラはある種の最適化を達成するために、しばしばいくつかの操作を適切な順序で調整し、予想外の現象を引き起こす。
実験現象
まず、この現象を一例で示します。一つのC〓〓NET Core 3.1コマンドラインプログラムでは、二つのグローバル変数aとbを定義し、スレッド1では、bとaを順次インクリメントします。このように、いつでもbはaまたはa+1に等しい。
逆アセンブリを見ると、1番目のc+=b文では、プログラムはbの値をレジスタに入れていますが、後の文はいずれもレジスタに格納された値を使っています。従って、コンパイラは実際にbに対する読取動作を統合して前置きしている。以下は反アセンブリ結果のセグメントです。
C〓言語標準のBaic concepts一章Execution orderの一節(参照:Baic concepts C〓langage specification)において、C〓〓の実行順序規範が言及されている。Cピラプログラムの副作用は以下のキーポイントの順序で保持されています。によるvolatileフィールドの読み書き ロック文 スレッドの作成と終了
C((xi)プログラムの実行順序は、以下の条件を満たす場合、実行環境によって任意に調整できます。は同じスレッド内にあり、データの依存関係は保留されている。つまり、結果は文が順番に実行される場合と一致します。 初期化順序の規則は保持されている。 は、volatileフィールドの読み書きに対して、副作用の順序が保持されている。
上記の副作用は以下を含む。 volatileフィールドを読み込みまたは書き込みする 書き込み非volatile変数 外部リソース を書き込みます。投げ異常
これにより、C〓〓プログラムでは非volatile変数に対する読み取り順序が調整される可能性がある。一つのスレッドだけがこの変数を操作する場合、この順序の調整は結果に影響しないことを保証します。しかし、他のスレッドが変数を修正している場合、読み取りの順序は確定されません。
したがって、複数のスレッドが同時にアクセスされる場合、値のリアルタイム性に要求される変数は、volatile変数として設定されるべきである。上記の実験における静的変数aとbをvolatile変数に変更すると、Release設定であってもコマンドラインの出力は現れません。つまり、2つの変数の読み取り順序は元のステートメント順序に合致します。
結論
C((xi)プログラムでは、非volatile変数を読み込む順序は、環境によって任意に調整される場合があります。ある変数が読み込まれると他のスレッドに書き込まれますが、その読み込み結果のリアルタイム性のために、この変数をvolatile変数に設定します。
締め括りをつける
ここでは、Cの実行順に関する潜在的な問題について紹介します。Cの実行順に関する問題がもっと多いので、私たちの以前の文章を検索したり、下記の関連記事を見たりしてください。これからもよろしくお願いします。
プログラムを作成する時、人々の直感的な感覚は、プログラムの実行順序は文の順序によって行われると思われます。しかし、多くのプログラミング言語の仕様は、実際の実行順序とステートメントの作成順序が一致しないことを可能にする。実際、コンパイラはある種の最適化を達成するために、しばしばいくつかの操作を適切な順序で調整し、予想外の現象を引き起こす。
実験現象
まず、この現象を一例で示します。一つのC〓〓NET Core 3.1コマンドラインプログラムでは、二つのグローバル変数aとbを定義し、スレッド1では、bとaを順次インクリメントします。このように、いつでもbはaまたはa+1に等しい。
static int a = 0;
static int b = 0;
static void Thread1()
{
while (true)
{
++b;
++a;
}
}
スレッド2では、aの値を先に読み取り、他の動作を行い、bの値を読み出す。ステートメントが必ず順序で実行される場合、読み取ったbの値は、読み取ったaの値よりも更新されるべきであり、bは必然的にaより大きいか等しい(bがオーバーフローしていない限り)。プログラムを作成して、b
static int c = 0;
static void Thread2()
{
while (true)
{
c += b;
var localA = a;
c += b;
var localB = b;
if (localA > localB)
{
Console.WriteLine($"a={localA} b={localB}");
}
}
}
メインプログラムを作成し、上記の2つのスレッドを起動します。
static void Main(string[] args)
{
Task.Run(Thread1);
Task.Run(Thread2);
Console.ReadKey();
}
Debug構成を使って、プログラムをコンパイルして実行します。コマンドラインは出力されていません。私たちの予想に合っています。但し、Releaseを使って配置すると、大量出力が発生します。そのうち、aの値はbより1から5まで大きくなります。逆アセンブリを見ると、1番目のc+=b文では、プログラムはbの値をレジスタに入れていますが、後の文はいずれもレジスタに格納された値を使っています。従って、コンパイラは実際にbに対する読取動作を統合して前置きしている。以下は反アセンブリ結果のセグメントです。
00007FFB628A394D mov rcx,7FFB6292FBD0h
00007FFB628A3957 mov edx,1
00007FFB628A395C call 00007FFBC2387B10
00007FFB628A3961 mov esi,dword ptr [7FFB6292FC08h]
00007FFB628A3967 mov ecx,esi
00007FFB628A3969 add ecx,dword ptr [7FFB6292FC0Ch]
00007FFB628A396F mov dword ptr [7FFB6292FC0Ch],ecx
var localA = a;
00007FFB628A3975 mov edi,dword ptr [7FFB6292FC04h]
c += b;
00007FFB628A397B add ecx,esi
c += b;
00007FFB628A397D mov dword ptr [7FFB6292FC0Ch],ecx
if (localA > localB)
00007FFB628A3983 cmp edi,esi
00007FFB628A3985 jle 00007FFB628A394D
理論的分析C〓言語標準のBaic concepts一章Execution orderの一節(参照:Baic concepts C〓langage specification)において、C〓〓の実行順序規範が言及されている。Cピラプログラムの副作用は以下のキーポイントの順序で保持されています。
したがって、複数のスレッドが同時にアクセスされる場合、値のリアルタイム性に要求される変数は、volatile変数として設定されるべきである。上記の実験における静的変数aとbをvolatile変数に変更すると、Release設定であってもコマンドラインの出力は現れません。つまり、2つの変数の読み取り順序は元のステートメント順序に合致します。
結論
C((xi)プログラムでは、非volatile変数を読み込む順序は、環境によって任意に調整される場合があります。ある変数が読み込まれると他のスレッドに書き込まれますが、その読み込み結果のリアルタイム性のために、この変数をvolatile変数に設定します。
締め括りをつける
ここでは、Cの実行順に関する潜在的な問題について紹介します。Cの実行順に関する問題がもっと多いので、私たちの以前の文章を検索したり、下記の関連記事を見たりしてください。これからもよろしくお願いします。