最初に、静的グローバル変数は、変数をファイルのスコープに制限していることを除いて、グローバル変数と同じです。extern
つまり、このグローバル変数をキーワードを介して他のファイルで使用することはできません。
したがって、質問をグローバル変数と揮発性変数に減らすことができます。
今揮発性に:
のようconst
に、volatile
は型修飾子です。
このvolatile
キーワードは、特に非同期イベントがある場合に、コードが正しくなくなる可能性のあるコンパイラの最適化を防ぐために作成されました。
として宣言されたオブジェクトはvolatile
、特定の最適化では使用されない場合があります。
前の命令が同じオブジェクトから値を要求した場合でも、システムは常に、使用された時点で揮発性オブジェクトの現在の真の値を読み取ります。また、オブジェクトの値は割り当て直後に書き込まれます。これは、揮発性変数をCPUレジスタにキャッシュしないことを意味します。
Jobb博士には、揮発性に関するすばらしい記事があります。
Jobb博士の記事の例を次に示します。
class Gadget
{
public:
void Wait()
{
while (!flag_)
{
Sleep(1000); // sleeps for 1000 milliseconds
}
}
void Wakeup()
{
flag_ = true;
}
...
private:
bool flag_;
};
コンパイラがそれSleep()
が外部呼び出しであると判断したSleep()
場合、変数flag_の値を変更できない可能性があると想定します。flag_
したがって、コンパイラはの値をレジスタに格納する場合があります。そしてその場合、それは決して変わらないでしょう。ただし、別のスレッドがウェイクアップを呼び出した場合、最初のスレッドは引き続きCPUのレジスタから読み取っています。Wait()
目覚めることはありません。
では、変数をレジスターにキャッシュせず、問題を完全に回避しないのはなぜですか?この最適化により、全体として多くの時間を節約できることがわかりました。したがって、C / C ++では、volatile
キーワードを使用して明示的に無効にすることができます。
上記の事実flag_
はメンバー変数であり、グローバル変数(または静的グローバル)ではありません。例の後の説明は、グローバル変数(および静的グローバル変数)を扱っている場合でも正しい理由を示しています。
よくある誤解は、変数を宣言するvolatile
だけでスレッドセーフを確保できるというものです。変数に対する操作は、レジスターに「キャッシュ」されていなくても、アトミックではありません。
ポインタ付きの揮発性:
ポインターを使用すると揮発性であり、ポインターを使用するconstのように機能します。
タイプvolatile int *
の変数は、ポインターが指す変数が揮発性であることを意味します。
タイプの変数はint * volatile
、ポインター自体が揮発性であることを意味します。