web-dev-qa-db-ja.com

静的グローバル変数と静的揮発性変数の違いは何ですか?

ファイルスコープで静的グローバル変数と静的揮発性変数を使用しましたが、

どちらもISRとメインループによって更新され、メインループは変数の値をチェックします。

ここでは、最適化中にグローバル変数も揮発性変数も最適化されません。したがって、揮発性変数を使用する代わりに、グローバル変数が問題を解決します。

揮発性の代わりにグローバル変数を使用するのは良いですか?

静的揮発性を使用する特定の理由??

サンプルプログラムはどれもかなりのものです。

前もって感謝します..

28
Manoj Doubts

それらは異なるものです。私は、不安定なセマンティクスの専門家ではありません。しかし、ここで説明されていることは理にかなっていると思います。

グローバル

グローバルとは、問題の識別子がファイルスコープで宣言されていることを意味します。関数(goto-labelsが定義されている)、ファイル(グローバルが存在する)、ブロック(通常のローカル変数が存在する)、および関数プロトタイプ(関数パラメーターが存在する)と呼ばれるさまざまなスコープがあります。この概念は、識別子の可視性を構造化するためにのみ存在します。最適化とは関係ありません。

静的

staticは保存期間であり(ここでは説明しません)、ファイルスコープの内部リンケージ内で宣言された名前を付ける方法です。これは、1つの翻訳単位内でのみ必要な関数またはオブジェクトに対して実行できます。典型的な例は、受け入れられたパラメーターを出力するhelp関数で、同じ.cファイルで定義されたmain関数からのみ呼び出されます。

6.2.2/2C99ドラフト:

オブジェクトまたは関数のファイルスコープ識別子の宣言にストレージクラスの識別子staticが含まれている場合、識別子には内部リンケージがあります。

内部リンケージとは、識別子が現在の翻訳単位の外では見えないことを意味します(上記のhelp関数のように)。

揮発性

揮発性は別のものです:(6.7.3/6

Volatileで修飾された型を持つオブジェクトは、実装で認識されない方法で変更されたり、その他の未知の副作用を持つ可能性があります。したがって、そのようなオブジェクトを参照する式は、5.1.2.3で説明されているように、抽象マシンのルールに従って厳密に評価されなければなりません。さらに、すべてのシーケンスポイントで、オブジェクトに最後に格納された値は、前述の未知の要因によって変更された場合を除き、抽象マシンで規定された値と一致するものとします。

この規格は、volatileが冗長になる例(5.1.2.3/8)の優れた例を提供します。

実装は、抽象セマンティクスと実際のセマンティクスの1対1の対応を定義する場合があります。すべてのシーケンスポイントで、実際のオブジェクトの値は抽象セマンティクスで指定された値と一致します。その場合、キーワードvolatileは冗長になります。

シーケンスポイントは、abstract machineに関する副作用の影響が完了するポイントです(つまり、メモリセル値などの外部条件は含まれません)。 &&||の右側と左側の間、;の後、関数呼び出しから戻ることは、たとえばシーケンスポイントです。

abstract Semanticsは、コンパイラが特定のプログラム内のコードのシーケンスのみを見ると推測できるものです。最適化の効果はここでは無関係です。 実際のセマンティクスオブジェクトへの書き込み(メモリセルの変更など)による副作用の影響を含めます。オブジェクトをvolatileとして認定するということは、常にオブジェクトの値をメモリから直接取得することを意味します(「未知の要因によって修正された」)。標準はどこにもスレッドに言及していないため、変更の順序や操作の原子性に依存する必要がある場合は、プラットフォームに依存する方法を使用してそれを保証する必要があります。

概要をわかりやすくするために、Intelには優れた記事があります here

私は今どうすればいい?

ファイルスコープ(グローバル)データをvolatileとして宣言し続けます。グローバルデータ自体は、変数の値がメモリに保存された値と等しくなることを意味しません。また、staticは、オブジェクトを現在の翻訳単位(現在の.cファイルと、それに含まれるすべてのファイル)に対してのみローカルにします。

最初に、静的なグローバル変数は、変数をファイルのスコープに制限していることを除いて、グローバル変数と同じであることに言及します。つまりexternキーワードを介して他のファイルでこのグローバル変数を使用することはできません。

したがって、質問をグローバル変数と揮発性変数に減らすことができます。

今揮発性に:

constと同様に、volatileは型修飾子です。

volatileキーワードは、特に非同期イベントがある場合に、コードが正しくない可能性があるコンパイラーの最適化を防ぐために作成されました。

volatileとして宣言されたオブジェクトは、特定の最適化では使用できません。

システムは、以前の命令が同じオブジェクトから値を要求した場合でも、使用される時点で常に揮発性オブジェクトの現在の真の値を読み取ります。また、オブジェクトの値は割り当て時にすぐに書き込まれます。つまり、揮発性変数のCPUレジスターへのキャッシュはありません。

Dr。Jobb'sにはvolatileに関するすばらしい記事があります

Dr. 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_の変数は、ポインター自体が揮発性であることを意味します。

42
Brian R. Bondy

「volatile」キーワードは、その変数を含むコードに対して特定の最適化を行わないようコンパイラーに提案します。グローバル変数のみを使用する場合、コンパイラがコードを誤って最適化することを妨げるものは何もありません。

例:

#define MYPORT 0xDEADB33F

volatile char *portptr = (char*)MYPORT;
*portptr = 'A';
*portptr = 'B';

「揮発性」がないと、最初の書き込みが最適化されます。

14

Volatileキーワードは、変数がキャッシュされないことを確認するようコンパイラーに指示します。すべてのスレッド間で一貫した値を得るために、すべてのアクセスは一貫した方法で行われる必要があります。変更のループチェック中に変数の値が別のスレッドによって変更される場合、通常の変数値が特定の時点でキャッシュされないという保証はなく、変数は揮発性にする必要がありますそのままであると仮定します。

ウィキペディアの揮発性変数

4
Krunch

Friolの答えを+1します。 CのvolatileはJavaのvolatileではありません。

したがって、最初に、コンパイラはプログラムのデータフローに基づいて多くの最適化を行うことができ、Cの揮発性はそれを防ぎ、毎回その場所に本当にロード/ストアすることを保証します(例えば、それを消去するレジスタを使用する代わりに) 。 friolが指摘したように、メモリをマップしたIOポートがある場合に役立ちます。

Cの揮発性は、ハードウェアキャッシュやマルチスレッドとは関係ありません。メモリフェンスは挿入されません。また、2つのスレッドがメモリフェンスにアクセスする場合、操作の順序にはまったく保証がありません。ただし、Javaのvolatileキーワードはそれを正確に行います。必要に応じてメモリフェンスを挿入します。

3
Piotr Lesnicki

それらは現在の環境で異なることはありませんが、微妙な変更が動作に影響を与える可能性があります。

  • 異なるハードウェア(より多くのプロセッサ、異なるメモリアーキテクチャ)
  • 最適化された新しいバージョンのコンパイラ。
  • スレッド間のタイミングのランダムな変動。問題は1,000万回に1回しか発生しない場合があります。
  • さまざまなコンパイラ最適化設定。

長い目で見れば、最初は適切なマルチスレッド構造を使用する方が、たとえそれがなくても今のところ機能しているように見えても、はるかに安全です。

もちろん、プログラムがマルチスレッドでない場合は問題ありません。

3
Darron