コンテンツにスキップ

最終更新:2026-10-12

C++ volatile キーワード

著者について:

個人公式アカウント(WeChat):

wechat

1.volatile

volatile 修飾された変数は、その読み書きを行う際に、観測可能な副作用**を引き起こします。そして、この観測可能な副作用は、**プログラム外の要因によって決定されます。

2.volatileの応用

(1)並行デバイスのハードウェアレジスタ(例:ステータスレジスタ)。 あるデバイスを初期化するとします。このデバイスのあるレジスタが0xff800000だとします。

int  *output = (unsigned  int *)0xff800000; //定义一个IO端口;  
int   init(void)  
{  
    int i;  
    for(i=0;i< 10;i++)
    {  
    *output = i;  
    }  
}
コンパイラの最適化を経て、コンパイラは前のループは最終結果に全く影響しない無駄なものだと判断します。最終的にはoutputというポインタに9を代入するだけなので、コンパイラが最終的にコンパイルするコードは次に相当します:
int  init(void)  
{  
    *output = 9;  
}
もしこの外部デバイスの初期化プロセスが、上のコードのように順番に値を代入しなければならないものであれば、明らかに最適化の過程では目的を達成できません。逆に、このポートに対して繰り返し書き込むのではなく繰り返し読み取る場合も、結果は同じです。最適化後のコンパイラは、あなたのコードによるこのアドレスへの読み取りを1回しか行わないかもしれません。しかしコードの見た目上は何の問題もありません。このようなとき、volatileを使ってコンパイラにこの変数は不安定なものであり、この変数に遭遇しても最適化しないよう伝えるべきです。

(2)割り込みサービスルーチンの中でアクセスされる変数;

static int i=0;

int main()
{
    while(1)
    {
    if(i) dosomething();
    }
}

/* Interrupt service routine */
void IRS()
{
    i=1;
}
上のサンプルプログラムの意図は、割り込みが発生したときに割り込みサービスルーチンIRSが割り込みに応答してプログラム変数iを変更し、main関数の中でdosomething関数を呼び出せるようにすることです。しかし、コンパイラはmain関数の中でiが変更されていないと判断するため、iからあるレジスタへの読み取りを1回しか行わず、その後の毎回のif判断にはこのレジスタの中の「iのコピー」だけを使う可能性があり、その結果dosomethingは永遠に呼び出されなくなります。変数iにvolatileを付けると、コンパイラは変数iの読み書き操作が最適化されないことを保証し、変数iが外部のプログラムによって変更された後、元のプログラムで速やかにそれを感知できることを保証します。

(3)マルチスレッドアプリケーションで複数のタスクから共有される変数。 複数のスレッドがある変数を共有するとき、その変数の値はあるスレッドによって変更されるため、volatileで宣言すべきです。その役割は、コンパイラの最適化によって変数がメモリからCPUレジスタへロードされるのを防ぐことです。あるスレッドが変数を変更した後、他のスレッドに速やかに同期されずプログラムのエラーを引き起こします。volatileの意味は、コンパイラにこの変数を操作するたびに必ずメモリから本当に値を読み出すようにさせ、すでにレジスタにある値を使わせないことです。例は次の通りです:

volatile  bool bStop=false;  //bStop 为共享全局变量  
//第一个线程
void threadFunc1()
{
    ...
    while(!bStop){...}
}
//第二个线程终止上面的线程循环
void threadFunc2()
{
    ...
    bStop = true;
}
2番目のスレッドで1番目のスレッドのループを終了させたい場合、bStopをvolatileで定義しないと、このループは無限ループになります。なぜならbStopはすでにレジスタに読み込まれており、レジスタの中のbStopの値は永遠にFALSEにならないからです。volatileを付けると、プログラムの実行時に毎回メモリからbStopの値を読み出すため、無限ループにはなりません。

volatileの応用シナリオを理解しているかどうかは、C/C++プログラマと組込み開発プログラマを見分ける有効な方法です。組込みをやる者たちはよくハードウェア、割り込み、RTOSなどと付き合い、これらにはすべてvolatile変数が必要です。volatileを理解していなければ、プログラム設計に災難をもたらすでしょう。

3.volatileのよくある質問

次の質問で、面接者がvolatileを本当に理解しているかどうかを確かめられます。 (1)ある引数はconstかつvolatileにできますか?なぜですか? できます。例は読み取り専用のステータスレジスタです。予期しない形で変更される可能性があるためvolatileです。プログラムがそれを変更しようとすべきではないためconstです。

(2)ポインタはvolatileにできますか?なぜですか? できます。これはあまり一般的ではありませんが。例は、割り込みサービスルーチンがあるbufferを指すポインタを変更する場合です。

(3)次の関数には何の誤りがありますか?

int square(volatile int *ptr) 
{ 
return *ptr * *ptr; 
} 
このコードは少しひねくれています。その目的はポインタptrが指す値の2乗を返すことですが、ptrはvolatile型の引数を指しているため、コンパイラは次のようなコードを生成します:
int square(volatile int *ptr) 
{ 
int a,b; 
a = *ptr; 
b = *ptr; 
return a * b; 
} 
*ptrの値は予期しない形で変更される可能性があるため、aとbは異なる値になるかもしれません。その結果、このコードが返すのは期待した2乗値ではない可能性があります!正しいコードは次の通りです:
long square(volatile int *ptr) 
{ 
int a=*ptr; 
return a * a; 
} 

4.volatileの使用

  • volatile キーワードは型修飾子であり、これで宣言された型の変数は、コンパイラにとって未知の要因(オペレーティングシステム、ハードウェア、他のスレッドなど)によって変更され得ることを示します。したがって、volatileを使って、コンパイラにそのようなオブジェクトを最適化すべきでないと伝えます。

  • volatile キーワードで宣言された変数は、アクセスのたびにメモリから値を取り出さなければなりません(volatileで修飾されていない変数は、コンパイラの最適化によってCPUレジスタから値を取る可能性があります)

  • const は volatile であり得ます(例:読み取り専用のステータスレジスタ)
  • ポインタは volatile であり得ます

コード学習:

関連トピック

コメント