組み込みC/C++

C/C++リテラシー向上のためのページ

volatileの使い方①

型修飾子にはconstとvolatile(人によっては型修飾子はlongとかshortのサイズ修飾のキーワードを指している場合があるようですが、ここではK&Rに従います。)がありますが、volatileを使う事はそうそうないかと思います。しかしマイコンの周辺機能を使用するエンジニアはよく知っておく必要があるのがvolatileです。volatile の役割は以下となります。

①処理系(コンパイラ)で行われる最適化を抑制する。

②暗黙の型変換を許可しない。

です。①は一般的ですが、②はあまり知られていない気がします。C++のexplicitの役割を兼ね備えています。

さてコンパイラで行われる最適化を抑制するとありますが、そんなプログラムの内容に影響を与えるような最適化が行われる事があるのか?それダメじゃないですか?と思ってしまいます。だいたい最適化ってどういったものがあるのでしょうか?最適化は主に二つの視点から行われます。

①リソース(ROM/RAM)の使用量を減らす。

②実行速度を早くする。

です。では、いくつか最適化対象となるようなコードとvolatileの効用を以下に記します。

 

Pattern 1

size_t iAddr = 0x00EFFFFF;
unsigned char *piAddr = (unsigned char*)iAddr;

*piAddr = 0x00;
*piAddr = 0x01;
*piAddr = 0x02;
...

 

同じ命令が並んでいますが、実際に意味があるのは最後の*piAddr = 0x02;だけのように見えます。そのためコンパイラは*piAddr = 0x00; *piAddr = 0x01;を消して(dead code elimination)アセンブリにします。(ただし、最適化はコンパイラが独自に行いますのでこれが確実に行われるかは判りません。) こういった処理は周辺機能のレジスタなら十分にありえます。この時volaltile unsigned char *piAddrとして最適化を抑制します。 

Pattern 2

unsigned char *piTmpData = (unsigned char *)0x00EFFFFF;
unsigned char iData = 0x01;

*piTmpData = iData;
...
//以降piTmpDataを使用しない

 

 これもPattern1とほぼ同じです。コンパイラはローカル変数*piTmpDataが右辺で使用されない事から*piTmpData = iData; という代入式を無駄な処理と判断し、この式を消してアセンブリにします。これも周辺機能のレジスタならありえます。これもvolatileの出番です。

Pattern 3

int iData;
int iCnt;

for( iCnt = 0 ; iCnt < 100 ; iCnt++ ){
iData = iCnt;
}
...
//以降iDataを使用しない

 

 waitの処理でこういった処理は見られますがこれも、コンパイラは無駄な処理として全部消してアセンブリにします。(もう一度書きますが、最適化はコンパイラが独自に行うのでこれが確実に行われるかは判りません。)こういったwait処理はレジスタを設定してから次の命令まで何マイクロ空けろとデータシートに書かれている場合に行いますがこの場合もvolatileの出番です。 

Pattern 4

unsigned char *piAddr;

piAddr = (*unsigned char)0x00EFFFFF;

while(*piAddr == 0){
...
}
...
 

 piAddrがポートレジスタだった場合、状態を変えるのは外部要因になりますが内部処理としてpiAddrの指す先に汎用レジスタが使われてしまう最適化が行われたらどうなるでしょうか?外部要因で変わるはずの0x00EFFFFF上の値が汎用レジスタに置き換わっている為、外部要因の値の変更が行われず、無限Loopとなってしまいます。(ローカル変数はスタックになるくらいだと思っていたのが、汎用レジスタの妙技で次から次へと変数の格納先が変わることがあります。コンパイラがどういったレジスタの使い方をするか正直未知数です。)こういった場合にvolatile unsigned char *piAddr;をして、この最適化を抑制します。

 

なお補足しておきますが、unsigned char * volatile piAddr; とするとアドレスが最適化されないという意味になります。基本的にはアドレスの指す先が最適化されないというのが一般的ではないかと思います。

 

まとめ

・周辺機能のアドレスやレジスタを格納する時はvolatileをつける。

・wait処理はvolatileをつける。

です。他にもコンパイラによって特殊な最適化があるかと思いますのでそんな時はvolatileをつけてみてください。最適化はコンパイラが変わった時の落とし穴となります。この手のバグは本当に見つけにくいので必要なところをよく見極めましょう。コンパイラのリリースノートにはよく注目するべきです。