の定義 volatile
volatile
コンパイラに通知せずに変数の値が変更される可能性があることをコンパイラに伝えます。したがって、Cプログラムが値を変更していないように見えるため、コンパイラは値が変更されなかったと想定することはできません。
一方、それは変数の値がコンパイラーが知らない他の場所で必要とされる(読み取る)ことを意味します。したがって、変数へのすべての割り当てが実際に書き込み操作として実行されることを確認する必要があります。
ユースケース
volatile
必要なとき
- ハードウェアレジスタ(またはメモリマップドI / O)を変数として表す-レジスタが読み取られない場合でも、コンパイラは「愚かなプログラマ。自分の変数に値を保存しようと考えて、書き込み操作をスキップしないでください」私たちが書き込みを省略しても気づかないでしょう。」逆に、プログラムが変数に値を書き込まない場合でも、その値はハードウェアによって変更される可能性があります。
- 実行コンテキスト間で変数を共有する(ISR /メインプログラムなど)(@kkramoの回答を参照)
の影響 volatile
変数が宣言されるとvolatile
、コンパイラーは、プログラムコード内の変数へのすべての割り当てが実際の書き込み操作に反映され、プログラムコード内のすべての読み取りが(マップされた)メモリから値を読み取ることを確認する必要があります。
不揮発性変数の場合、コンパイラは変数の値が変化するかどうか/いつ変化するかを知っていると想定し、さまざまな方法でコードを最適化できます。
1つは、CPUレジスタの値を保持することにより、コンパイラーがメモリへの読み取り/書き込みの回数を減らすことができることです。
例:
void uint8_t compute(uint8_t input) {
uint8_t result = input + 2;
result = result * 2;
if ( result > 100 ) {
result -= 100;
}
return result;
}
ここで、コンパイラはおそらくresult
変数にRAMを割り当てず、CPUレジスタ以外の中間値を保存しません。
result
揮発性の場合result
、Cコード内で出現するたびに、コンパイラはRAM(またはI / Oポート)へのアクセスを実行する必要があり、パフォーマンスが低下します。
第二に、コンパイラーは、パフォーマンスおよび/またはコードサイズのために不揮発性変数の操作を並べ替えることができます。簡単な例:
int a = 99;
int b = 1;
int c = 99;
再注文可能
int a = 99;
int c = 99;
int b = 1;
値99
を2回ロードする必要がないため、アセンブラー命令を保存できます。
a
、b
およびc
が揮発性の場合、コンパイラーは、プログラムで指定されたとおりに正確な順序で値を割り当てる命令を発行する必要があります。
他の典型的な例は次のとおりです。
volatile uint8_t signal;
void waitForSignal() {
while ( signal == 0 ) {
// Do nothing.
}
}
この場合、そうでsignal
はない場合、volatile
コンパイラはwhile( signal == 0 )
無限ループであると「考える」ことになり(ループ内のsignal
コードによって変更されることはないため)、同等のものを生成する可能性があります
void waitForSignal() {
if ( signal != 0 ) {
return;
} else {
while(true) { // <-- Endless loop!
// do nothing.
}
}
}
volatile
値の思いやりのある取り扱い
前述のように、volatile
変数は実際に必要とされるよりも頻繁にアクセスされるとパフォーマンスが低下する可能性があります。この問題を軽減するには、次のように不揮発性変数に割り当てて値を「不揮発性」にすることができます。
volatile uint32_t sysTickCount;
void doSysTick() {
uint32_t ticks = sysTickCount; // A single read access to sysTickCount
ticks = ticks + 1;
setLEDState( ticks < 500000L );
if ( ticks >= 1000000L ) {
ticks = 0;
}
sysTickCount = ticks; // A single write access to volatile sysTickCount
}
これは、あなたがたときに、可能な限り迅速には、同じハードウェアやメモリを複数回アクセスしていないようにしたいISRの中に特に有益であるかもしれないあなたは、あなたのISRの実行中に値が変更されませんので、それが必要とされていません知っています。これはsysTickCount
、上記の例のように、ISRが変数の値の「プロデューサー」である場合に一般的です。AVRでは、関数doSysTick()
がメモリ内の同じ4バイトにアクセスすることは特に苦痛です(4命令=アクセスあたり8 CPUサイクルsysTickCount
)2回ではなく5回または6回、プログラマは値がそうでないことを知っているので実行中に他のコードから変更されdoSysTick()
ます。
このトリックでは、本質的にコンパイラが不揮発性変数に対して行うのとまったく同じことを行います。つまり、必要な場合にのみメモリから読み取り、しばらくの間レジスタに値を保持し、必要な場合にのみメモリに書き戻します; 今回は、あなたは /書き込みが読み取ったときに/場合は、より良いコンパイラよりも知っている必要がありますが、この最適化タスクからコンパイラを和らげるので、起こるとそれを自分で行います。
の制限 volatile
非原子アクセス
volatile
マルチワード変数へのアトミックアクセスを提供しません。そのような場合、を使用することに加えて、他の手段で相互排除を提供する必要がありますvolatile
。AVRでは、ATOMIC_BLOCK
from コール<util/atomic.h>
または単純なcli(); ... sei();
コールを使用できます。それぞれのマクロもメモリバリアとして機能します。これは、アクセスの順序に関して重要です。
実行順序
volatile
他の揮発性変数に関してのみ厳密な実行順序を課します。これは、たとえば
volatile int i;
volatile int j;
int a;
...
i = 1;
a = 99;
j = 2;
は、最初に1を割り当てi
、次に 2を割り当てることが保証されていj
ます。ただし、間に割り当てられることは保証されませんa
。コンパイラは、コードスニペットの前後に、基本的にa
。
上記のマクロのメモリバリアがなければ、コンパイラは翻訳を許可されます。
uint32_t x;
cli();
x = volatileVar;
sei();
に
x = volatileVar;
cli();
sei();
または
cli();
sei();
x = volatileVar;
(完全を期すためにvolatile
、すべてのアクセスがこれらのバリアで囲まれている場合、sei / cliマクロによって暗示されるようなメモリバリアは、実際にはの使用を不要にする可能性があると言わなければなりません。)