c/c++ 言語で

volatile宣言

を使ったことはありますか?

 

組み込み系処理ではよく出てきますが、Web系エンジニアの方は聞いたことがない思います。

 

volatileは

コンパイラの最適化を抑止するための修飾子

です。

 

「コンパイラの最適化を抑止するなんて、デメリットしかないじゃないか!」

と思うかもしれませんが、組み込み系やマルチスレッド系などの、割り込み処理が入るプログラムでは必須のテクニックなのです。

この記事でわかること
  • volatile(ボラタイル)修飾子をつけた場合とつけなかった場合の違い
  • どんな時にvolatile修飾子をつける必要があるか?

volatileをつけた場合とつけない場合の違い

まず、volatile修飾子をつけた場合の処理の流れです。

下の図は、変数aを定義した後、
何らかの理由で処理が停止した場合の変数aの流れを表しています。

まず、定義された変数aは「レジスタ」と呼ばれるエリアに格納されます。

ここで処理が停止すると、レジスタ内のデータは一旦「スタック」と呼ばれるエリアに退避されます。

 

処理を再開すると、変数aは「スタック」から「レジスタ」に移動します。

これがコンパイラによる最適化をされていない、通常の流れです。

 

では、volatile修飾子をつけなかった場合。

つまり、コンパイラによる最適化が行われた場合、何が起きるのでしょうか?

volatile宣言なしでコンパイラに最適化された場合

先ほどと同様、定義された変数aはレジスタに格納されます。

ここで処理が停止すると、レジスタに格納されていた変数aは消滅してしまうのです。

 

これは、コンパイラによって最適化された結果、スタックにデータを戻すことは不要と判断されてしまったためです。

処理が復帰した時、レジスタの変数aの入っているデータを参照してしまうとプログラムエラーとなります。

 

もちろん、最適化を行うことによって処理スピードが早くなるというメリットはあります。

しかし、人間の手によって処理を一時停止させたり、複数の処理が同時に動く場合などではおせっかいな最適化となります。

volatileをつけなくてもいい場合

ここからは、volatile宣言が不要なケースを紹介します。

 

下の図のような処理があるとします。

よくあるシーケンス図ですね。

開始 → 処理A→B→C → 終了

 

という流れですが、各処理から一時停止処理にジャンプすることができます。

処理を再開する場合は、ジャンプした処理の先頭から再スタートします。

 

たとえば、処理Bを行なっている最中に一時停止命令が来た場合

  1. 処理Bを一旦やめ、
  2. 一時停止の処理の先頭にジャンプし、
  3. 一時停止処理を行った後、
  4. 再び処理Bの先頭に戻る

このような処理を行っています。

非常に危険な不定値の参照

ここで大事なことなのですが、

不定値が入っている変数を参照すると、この処理はプログラムエラーとなります。

 

変数を定義するときは、最初に何か初期値を入れてから参照しますよね?

初期値の入っていない変数には「不定値」が入っています。

 

この不定値を参照してしまうと、例外処理がない場合は処理が停止することがあります。

システム開発をする際は、不定値の参照は非常に危険です。

volatile宣言あり・なしでの違いは?

さて、下の図のように共通変数aを使った時の流れを見てみましょう。

  1. 処理開始と同時に、変数aを宣言します。
  2. 処理Aにて、aに2が代入されます( a = 2 ; )
  3. 処理Cにて、変数aのデータを参照します。

というプログラムです。

aに値が入っていれば大丈夫

 

さてここで、上の図のようなルートの処理を行ったとしましょう。

  1. 処理Aを始めたものの、処理Aの途中で一時停止となる。
  2. 再び処理Aを始めから再スタート
  3. その後は、処理B→処置Cと順調に進んだ。

この時、変数aでは下の図のようなことが起きています。

変数aにvolatile宣言がついていなかったために、一時停止処理にて

変数aに不定値が入ってしまいました。

(コンパイラによる最適化のため、変数aのデータ保持は不要と判断された)

 

しかしこの場合、一時停止から再復帰後、

再び戻って来た処理Aにて変数aに2が代入されます。

 

その後、処理Cにて変数aが参照されますが、
ここではちゃんとした数字(a=2)が入った状態で参照されるので、
無事に処理を終えることができます。

不定値の入ったaを参照するとプログラムエラー

さて、一方で下の図のようなルートではどうでしょう?

処理A→B→Cと順調に進みましたが、
処理Cの途中で一時停止となります。

 

再び処理Cをやり直しますが・・・

 

ここで、一時停止処理から再復帰した処理Cでは、このようなことが起きています。

一時停止処理にて、変数aに不定値が入ってしまいした。

再び処理Cから再開しますが、処理Cにて変数aを参照すると、

不定値が入っているためプログラムエラーとなってしまいます。

 

もしも、変数aをvolatile宣言していれば、一時停止を経由したとしても不定値は入らないので、プログラムエラーにはなりませんでした。

Volatile宣言まとめ

多くの場合は、使う必要のないvolatile宣言ですが、

  • 複数の関数をまたいで使用する変数がある
  • リプレースでコンパイラが変わる

このような場合は注意深くチェックする必要があると思います。

 

プログラムをしていてvolatile修飾子を使う人は少ないですが、わかりやすく解説しているブログはありませんでしたので、知識の整理も兼ねてこの記事を書きました。

参考になれば幸いです。

Twitterでフォローしよう

おすすめの記事