静的グローバル変数と静的揮発性変数の違いは何ですか?
質問
ファイルスコープで静的グローバル変数と静的揮発性変数を使用しましたが、
は両方ともISRとメインループによって更新され、メインループは変数の値をチェックします。
ここで最適化を行うと、グローバル変数も揮発性変数も最適化されません。したがって、揮発性変数を使用する代わりに、グローバル変数が問題を解決します。
では、volatileの代わりにグローバル変数を使用するのが良いでしょうか?
静的volatileを使用する具体的な理由は何ですか?
どんなプログラム例でも評価できるでしょう。
事前に感謝します。
解決
これらは異なるものです。私は、不安定なセマンティクスの専門家ではありません。しかし、ここで説明されていることは理にかなっていると思います。
グローバル
グローバルとは、問題の識別子がファイルスコープで宣言されていることを意味します。関数(goto-labelsが定義されている)、<!>#64257; le(グローバルが存在する)、ブロック(通常のローカル変数が存在する)、および関数プロトタイプ(関数パラメーターが存在する)と呼ばれるさまざまなスコープがあります。この概念は、識別子の可視性を構造化するためにのみ存在します。最適化とは関係ありません。
静的
static
は保存期間であり(ここでは説明しません)、ファイルスコープの内部リンケージ内で宣言された名前を指定する方法です。これは、1つの翻訳単位内でのみ必要な関数またはオブジェクトに対して実行できます。典型的な例は、受け入れられたパラメーターを出力するhelp
関数で、同じmain
ファイルで定義された.c
関数からのみ呼び出されます。
6.2.2 / 2 :
<!>#64257; leスコープの宣言 identi <!>#64257;オブジェクトまたは関数の場合 ストレージクラスspeci <!>#64257; erが含まれます 静的、identi <!>#64257; erには内部 リンケージ。
内部リンケージは、識別子が現在の翻訳単位の外側に表示されないことを意味します(上記のvolatile
関数のように)。
揮発性
揮発性は別のものです:( 6.7.3 / 6 )
volatile-quali <!>#64257; edを持つオブジェクト typeはmodi <!>#64257; 実装またはその他の 未知の副作用。したがって、 そのようなオブジェクトを参照する式 厳密に評価されるものとします 抽象機械の規則に従って、 5.1.2.3で説明されています。さらに、 すべてのシーケンスポイントで値が最後 オブジェクトに格納されている 抄録で規定されていること modi <!>#64257; edによる場合を除き、マシン 言及されている未知の要因 以前。
標準は、&&
が冗長になる例( 5.1.2.3/8 )の優れた例を提供します。
実装はde <!>#64257; ne a 間の1対1の対応 抽象的および実際の意味論:at すべてのシーケンスポイント、の値 実際のオブジェクトは同意します 仕様書に記載されているもの<!>#64257; セマンティクス。キーワード
||
冗長になります。
シーケンスポイントは、抽象マシンに関する副作用の影響が完了するポイントです(つまり、メモリセル値などの外部条件は含まれません)。 ;
と<=>の左右の間、<=>の後、関数呼び出しから戻ることは、たとえばシーケンスポイントです。
抽象セマンティクスは、コンパイラが特定のプログラム内のコードのシーケンスのみを見ると推測できるものです。最適化の効果はここでは無関係です。 実際のセマンティクスには、オブジェクトへの書き込み(メモリセルの変更など)による副作用の影響が含まれます。オブジェクトをvolatileとして修飾するということは、常にオブジェクトの値をメモリから直接取得することを意味します(<!> quot;未知の要因によって変更されたものとして<!> quot;)。標準はどこにもスレッドに言及していないため、変更の順序や操作の原子性に依存する必要がある場合は、プラットフォームに依存する方法を使用してそれを保証する必要があります。
概要を理解しやすくするために、インテルには優れた記事がありますこちら。
今何をすべきですか?
ファイルスコープ(グローバル)データをvolatileとして宣言し続けます。グローバルデータ自体は、変数の値がメモリに保存された値と等しくなることを意味しません。そして、静的はオブジェクトを作るだけです現在の翻訳単位に対してローカル(現在の<=>ファイルと、それに含まれる他のすべてのファイル)。
他のヒント
まず、静的なグローバル変数は、変数をファイルのスコープに制限していることを除いて、グローバル変数と同じであることに言及します。つまりextern
キーワードを介して他のファイルでこのグローバル変数を使用することはできません。
したがって、質問をグローバル変数と揮発性変数に減らすことができます。
今揮発性に:
const
と同様、volatile
は型修飾子です。
Sleep()
キーワードは、特に非同期イベントがある場合に、コードが正しくない可能性があるコンパイラーの最適化を防ぐために作成されました。
flag_
として宣言されたオブジェクトは、特定の最適化では使用できません。
システムは、以前の命令が同じオブジェクトから値を要求した場合でも、使用される時点で常に揮発性オブジェクトの現在の真の値を読み取ります。また、オブジェクトの値は割り当て時にすぐに書き込まれます。つまり、CPUレジスタへの揮発性変数のキャッシュはありません。
Dr. Jobb'sにはvolatileに関する素晴らしい記事があります。
Jobb博士の記事の例を次に示します。
class Gadget
{
public:
void Wait()
{
while (!flag_)
{
Sleep(1000); // sleeps for 1000 milliseconds
}
}
void Wakeup()
{
flag_ = true;
}
...
private:
bool flag_;
};
コンパイラは、Wait()
が外部呼び出しであることを認識すると、volatile int *
が変数flag_の値を変更できないと想定します。そのため、コンパイラはint * volatile
の値をレジスタに保存できます。そしてその場合、それは決して変わりません。ただし、別のスレッドがウェイクアップを呼び出す場合、最初のスレッドはまだCPUのレジスタから読み取りを行っています。 <=>ウェイクアップしません。
では、変数をレジスタにキャッシュしないで、問題を完全に回避しないのはなぜですか? この最適化により、全体的な時間を大幅に節約できることがわかりました。そのため、C / C ++では、<=>キーワードを使用して明示的に無効にすることができます。
<=>がグローバル変数(または静的グローバル)ではなく、メンバー変数であるという事実は重要ではありません。例の後の説明は、グローバル変数(および静的グローバル変数)を扱っている場合でも正しい推論を与えます。
一般的な誤解は、変数<=>を宣言するだけでスレッドの安全性を確保できるというものです。 <!> quot; cached <!> quotではない場合でも、変数の操作はアトミックではありません。レジスタ内
ポインターを使用した揮発性:
ポインター付きの揮発性、ポインター付きのconstのように動作します。
タイプ<=>の変数は、ポインターが指す変数が揮発性であることを意味します。
タイプ<=>の変数は、ポインター自体が揮発性であることを意味します。
<!> quot; volatile <!> quot;キーワードは、コンパイラがその変数を含むコードに対して特定の最適化を行わないことを示唆しています。グローバル変数を使用するだけの場合、コンパイラがコードを誤って最適化することを妨げるものはありません。
例:
#define MYPORT 0xDEADB33F
volatile char *portptr = (char*)MYPORT;
*portptr = 'A';
*portptr = 'B';
<!> quot; volatile <!> quot;がなければ、最初の書き込みが最適化されます。
volatileキーワードは、変数がキャッシュされないことを確認するようコンパイラーに指示します。すべてのスレッド間で一貫した値を得るために、すべてのアクセスは一貫した方法で行われる必要があります。変更のループを確認している間に変数の値が別のスレッドによって変更される場合、通常の変数値が特定の時点でキャッシュされないという保証がないため、変数を揮発性にする必要がありますそのままであると仮定します。
これらは現在の環境では異なることはありませんが、微妙な変更が動作に影響を与える可能性があります。
- 異なるハードウェア(より多くのプロセッサ、異なるメモリアーキテクチャ)
- 最適化されたコンパイラの新しいバージョン。
- スレッド間のタイミングのランダムな変動。問題は1,000万回に1回しか発生しない可能性があります。
- 異なるコンパイラ最適化設定。
長期的には、最初から適切なマルチスレッド構造を使用する方が、それらがなくても今のところ機能しているように見えても、はるかに安全です。
もちろん、プログラムがマルチスレッドではない場合は問題ではありません。
I friolの答えを+1。 CのvolatileはJavaのvolatileではないため、さまざまな答えに多くの混乱があるように思えるので、精度を追加したいと思います。
最初に、コンパイラーはプログラムのデータフローに基づいて多くの最適化を行うことができます.Cの揮発性はそれを防ぎ、毎回その場所に本当にロード/ストアすることを保証します(それを消去するレジスタを使用する代わりに)例)。 friolが指摘したように、メモリにマップされたIOポートがある場合に便利です。
CのVolatileは、ハードウェアキャッシュやマルチスレッドとは関係ありません。メモリフェンスは挿入されません。また、2つのスレッドがメモリフェンスにアクセスする場合、操作の順序にはまったく保証がありません。ただし、Javaのvolatileキーワードは、必要に応じてメモリフェンスを挿入します。
volatile variableは、割り当てられた値が定数ではないことを意味します。つまり、volatile変数を含む関数<!> quot; a = 10 <!> quot;関数はその関数の各呼び出しで1を追加し、常に更新された値を返します。
{
volatile int a=10;
a++;
}
上記の関数が何度も呼び出されると、変数aは10に再初期化されず、プログラムが実行されるまで常に更新された値が表示されます。
最初の出力= 10
11
12
などなど。