21

[編集] 背景を読むために、そして明確にするために、これは私が話していることです: volatile キーワードの紹介

組み込みシステム コードを確認する際によく見られるエラーの 1 つは、スレッド/割り込み共有データの volatile が省略されていることです。volatileしかし、私の質問は、アクセス関数またはメンバー関数を介して変数にアクセスするときに使用しないことが「安全」かどうかです。

簡単な例; 次のコードで...

volatile bool flag = false ;
void ThreadA()
{
    ...
    while (!flag)
    {
        // Wait
    }
    ...
}

interrupt void InterruptB()
{
    flag = true ;
} 

... flagThreadA の読み取りが最適化されないように、変数は揮発性でなければなりませんが、関数を介してフラグが読み取られた場合は...

volatile bool flag = false ;
bool ReadFlag() { return flag }
void ThreadA()
{
    ...
    while ( !ReadFlag() )
    {
        // Wait
    }
    ...
}

...flagまだ揮発性である必要がありますか? 揮発性であっても害がないことは理解していますが、私の懸念は、それが省略され、省略が発見されない場合です。これは安全でしょうか?

上記の例は自明です。実際のケース (および質問の理由) では、タスク オブジェクトの派生元である抽象クラス cTask が存在するように、RTOS をラップするクラス ライブラリがあります。このような「アクティブな」オブジェクトには通常、オブジェクトのタスク コンテキストで変更されるデータにアクセスするメンバー関数がありますが、他のコンテキストからはアクセスされます。そのようなデータが揮発性であると宣言されることは重要ですか?

私は、実際のコンパイラが何をするかよりも、そのようなデータについて何が保証されているかに本当に興味があります。私は多くのコンパイラをテストし、それらがアクセサを介した読み取りを最適化しないことを発見するかもしれませんが、ある日、この仮定が正しくないコンパイラまたはコンパイラ設定を見つけます. たとえば、関数がインライン化されている場合、そのような最適化は直接読み取りと変わらないため、コンパイラにとっては簡単であると想像できます。

4

5 に答える 5

12

私の C99 の解釈は、 を指定しない限りvolatile、変数が実際にいつどのようにアクセスされるかは実装定義であるということです。修飾子を指定すると、コードは抽象マシンvolatileの規則に従って動作する必要があります。

標準の関連部分は、6.7.3 Type qualifiers(揮発性記述) および5.1.2.3 Program execution(抽象マシン定義) です。

しばらく前から、変数を再度読み取る必要がある場合と、キャッシュされたコピーを使用しても問題ない場合を検出するヒューリスティックを多くのコンパイラが実際に持っていることを知っています。揮発性は、変数へのすべてのアクセスが実際にはメモリへのアクセスであることをコンパイラーに明確にします。volatile がないと、コンパイラは自由に変数を再読み取りしないようです。

inlineそして、ところで、アクセスを関数にラップしても、現在のコンパイル単位内のコンパイラによって関数がインライン化されていない可能性があるため、それは変わりません。

PS C++の場合、おそらく前者が基づいているC89をチェックする価値があります。C89は手元にありません。

于 2010-06-30T11:31:35.997 に答える
5

はい、それは重要です。
あなたが言ったvolatileように、共有メモリの最適化を壊すコードを防ぎます[C++98 7.1.5p8]
特定のコンパイラが現在または将来どのような最適化を行うかはわからないため、変数が volatile であることを明示的に指定する必要があります。

于 2010-06-30T11:30:16.260 に答える
1

C では、volatileキーワードはここでは必要ありません (一般的な意味で)。

ANSI C 仕様 (C89) のセクション A8.2「型指定子」から:

volatileオブジェクトには、実装に依存しないセマンティクスはありません。

Kernighan と Ritchieは、このセクションについて次のようにコメントしています (constおよびvolatile指定子を参照)。

オブジェクトを変更する明示的な試みを診断する必要がある場合を除いてconst 、コンパイラはこれらの修飾子を無視する場合があります。

volatileこれらの詳細を考えると、特定のコンパイラがキーワードをどのように解釈するか、または完全に無視するかどうかは保証できません。完全に実装に依存するキーワードは、どのような状況でも「必須」と見なされるべきではありません。

そうは言っても、K&R は次のようにも述べています。

の目的は、volatileそうしないと発生する可能性のある最適化を実装に強制的に抑制することです。

実際には、これは私が見た実質的にすべてのコンパイラが解釈する方法volatileです。変数を as として宣言するvolatileと、コンパイラは変数へのアクセスを最適化しようとしません。

ほとんどの場合、最新のコンパイラは、変数を安全にキャッシュできるかどうかを判断するのに非常に優れています。特定のコンパイラが最適化してはならないものを取り除いていることがわかった場合は、volatileキーワードを追加することが適切な場合があります。volatileただし、これにより、変数を使用する関数内の残りのコードに対してコンパイラが実行できる最適化の量が制限される可能性があることに注意してください。一部のコンパイラは、これに関して他のものよりも優れています。私が使用した組み込み C コンパイラの 1 つは、にアクセスする関数のすべての最適化をオフにしましたvolatileが、gcc などの他のコンパイラは、いくつかの限定的な最適化を引き続き実行できるようです。

アクセサー関数を介して変数にアクセスすると、関数が値をキャッシュするのを防ぐ必要があります。関数が自動インライン化されている場合でも、関数を呼び出すたびに関数を再呼び出しし、新しい値を再フェッチする必要があります。アクセサー関数を自動インライン化し、データの再フェッチを最適化するコンパイラーを見たことがありません。それが起こらないと言っているわけではありませんが (これは実装に依存する動作であるため)、それが起こることを期待するコードは書きません。2番目の例は、基本的に変数の周りにラッパーAPIを配置することであり、ライブラリはvolatile常に使用せずにこれを行います.

volatile全体として、Cでのオブジェクトの扱いは実装に依存します。ANSI C89仕様によると、それらについて「保証」されるものは何もありません。

あなたのコードはvolatile、スレッドと割り込みルーチンの間でオブジェクトを共有しています。volatile並列アクセスを処理するのに十分な能力を提供するコンパイラの実装 (私がこれまでに見たもの) はありません。2 つのスレッド (最初の例) が互いに足を踏み入れないことを保証するために、ある種のロック メカニズムを使用する必要があります (一方が割り込みハンドラーであっても、マルチ CPU またはマルチで並列アクセスを行うことができます)。 -コアシステム)。

于 2010-06-30T16:18:21.927 に答える
1

もちろん、2 番目の例では、変数「flag」の書き込み/変更は省略されています。書き込まれない場合は、揮発性である必要はありません。

主な質問について

すべてのスレッドが同じ関数を介して変数にアクセス/変更した場合でも、変数には引き続き volatile のフラグを付ける必要があります。

関数は、複数のスレッドで同時に「アクティブ」になることができます。関数コードが、スレッドによって取得されて実行される単なる青写真であると想像してください。スレッド B がスレッド A の ReadFlag の実行に割り込んだ場合、スレッド B は単に ReadFlag の別のコピーを実行します (別のスタック、別のレジスタの内容など、別のコンテキストで)。そうすることで、スレッド A での ReadFlag の実行が台無しになる可能性があります。

于 2010-06-30T11:17:02.703 に答える
0

編集:私はコードをあまりよく読んでいなかったので、これはスレッド同期に関する質問であり、volatile決して使用すべきではないと思いましたが、この使用法は問題ないように見えます(問題の変数が他にどのように使用されているかによって異なります、および割り込みが常に実行されているため、メモリのビューがスレッドが見るものと (キャッシュ) コヒーレントである場合volatile. 「関数呼び出しでラップする場合、修飾子を削除できますか?」の場合、受け入れられたこの質問を読んでいる人にとってvolatile、特定の非常に特殊なケース以外ではほとんど役に立たないことを知ることが重要であるため、元の回答を残しておきます。

詳細編集: RTOS の使用例では、揮発性を超えた追加の保護が必要になる場合があります。場合によってはメモリ バリアを使用するか、アトミックにする必要があります。注意してください(以下にあるLinuxカーネルのドキュメントリンクを見ることをお勧めしますが、Linuxはvolatileそのようなことには使用しません。おそらく正当な理由があります)。必要な場合と不要な場合の一部は、volatile実行している CPU のメモリ モデルに大きく依存し、多くの場合volatile十分ではありません。

volatileこれを行う方法は間違っています。このコードが機能することを保証するものではありません。この種の使用を意図したものではありません。

volatileメモリマップされたデバイスレジスタへの読み取り/書き込みを目的としていたため、その目的には十分ですが、スレッド間でのやり取りについて話している場合は役に立ちません。(特に、コンパイラは、実行中の CPU と同様に、いくつかの読み取りと書き込みの順序を変更するために声を上げています (これvolatileは、CPU に特別なことをするように指示しないため、非常に重要です (キャッシュをバイパスすることを意味する場合もありますが、それはコンパイラです) /CPU 依存))

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2016.htmlIntel 開発者記事CERTLinux カーネル ドキュメントを参照してください。

これらの記事の短いバージョンを、好きなように使用することは、悪いことvolatile悪いことの両方です。それはあなたのコードを遅くするので悪いです.それは実際にあなたが望むことをしないので間違っています.

実際には、x86 ではコードは があってもなくても正しく機能しますがvolatile、移植性はありません

編集:実際にコードを読んでください...これ、行うことを意図したものvolatileです。

于 2010-06-30T14:49:36.763 に答える