コンパイラには、最適化オプションというのがあるのは知っているよね。最適化の種類も、コードサイズに関するものや動作速度に関するものなど、いろいろある。
でも、時としてコンパイラの最適化が悪さをして、組込みソフトウエアが正しく動かなくなってしまうことがあるんだ。
具体的な例をあげて説明してみよう。
例1
メモリアドレス0xB8000040に配置されている16-bitレジスタ(RegA)があるとする。このレジスタの最下位ビットがセットされるまで、ポーリングループで監視するコードを例にとってみよう。
これをコンパイルして実行すると、意図しない結果となってしまう。具体的に言うと、ポーリング監視処理が働かず、ビットがセットされていないのに、funcWhenBitSet()が呼ばれる可能性があるんだ。
どこがおかしいのだろうか?ロジック的には問題はないし、コンパイル時にエラーもないよね。
原因は、コンパイラの最適化なんだ。ポインタ変数regAは、while()でしか使われていない。また、実際にはregAの指すアドレスにはハードウエアのレジスタがあり、ビット値は変化するけど、ソースコード上は操作していないからregAの内容が「変化しない」ように見える。このため、コンパイラは最適化の過程で「regAは使われていない変数」という判断を下して、カットしてしまうんだ。
次に、この不具合をデバッグすることを考えてみよう。ポーリングがうまく働かないようなので、レジスタの値をログ表示させてみたくなるよね。
すると、ポーリングが動くようになる。あれ?なぜだかわかるかな?
これはprintf()で「regAが使われている」ことによって、最適化をかけてもポーリング処理が削除されなくなるからなんだ。「ログを削除すると不具合が起こるが、ログを追加すると起こらない」という非常にやっかいな(デバッグしづらい)パターンに陥ってしまう。
では、どうすればよいのだろうか。それは「変数regAを最適化してほしくない」ことを明示的に示せばいいよね。これをしてくれるのが「volatile修飾子」だ。constなどと同様に、ANSI規格のC言語で定義されている修飾子で「コンパイラによる最適化を抑止する」というものなんだ。
以下のように変数regAを宣言するところにvolatileを追加する。
こうするとコンパイラは変数regAについて最適化を行わなくなり、ポーリングが正常に動作するようになるんだ。もうひとつ別の例を見てみよう。
例2
0xB0000050に配置されているデータ書き込み用8-bitレジスタ(RegB)があるとする。さらにデータシートに、このレジスタは一度書き込んでから次に書き込むまでに一定時間ウェイトする必要がある、と注意書きが書かれていたとしよう。これを以下のようにコーディングしてみるよ。
変数regBの宣言にはちゃんとvolatileがついているよね。これはOKだ。しかし、これではまだ問題があるんだ。わかるかな?
それは、forの空ループなんだ。最初の例でも説明したけど、変数iはこのループでしか使われていない。コンパイラは、他の箇所で参照されていない変数のループだから意味がないとみなして削除してしまうんだ。その結果、想定したウェイトが得られずデータが正しく書き込めないという結果になってしまう。
このように、volatile修飾子は組込みソフトウエアでは非常に重要なんだ。ハードウエアレジスタに対するアクセスを行う変数には必ずつけておく必要があるよ。
補足
ちなみに、(例2)の空ループによるウェイトは好ましい使い方ではない。なぜなら、CPUの処理速度によって確保できるウェイト時間が変動してしまうからだ。
あるシステムでは動いても、他のシステムに移植したときに正しく動かなくなるのでは困ってしまうからね。でも、次のレジスタ書き込みまでの区間は、タスク切替えを禁止したい、さらにOSのシステムコール呼び出しもしたくない、といった特殊な場合には、あえてこのような空ループを使うこともあるんだ。
-
- 第1回 組込みシステムのこれから
- 第2回 IoTの成功はセキュリティ次第
- 第3回 組込みでもGPUやFPGAと早めに親しんでおこう
- 第4回 電子産業の紅白歌合戦、CEATECで垣間見えた未来
- 第5回 小口開発案件の集合市場、IoTの歩き方(上)
- 第6回 小口開発案件の集合市場、IoTの歩き方(下)
- 第7回 徹底予習:AI時代の組込みシステム開発のお仕事
- 第8回 いまどきのセンサー(上):ありのままの状態を知る
- 第9回 いまどきのセンサー(下):データを賢く取捨選択する
- 第10回 組込みブロックチェーンの衝撃(上)
- 第11回 組込みブロックチェーンの衝撃(下)
- 第12回 エネルギーハーベスティングの使い所、使い方
- 第13回 「人を育てる」から「道具を育てる」へ、農業から学ぶAI有効活用法
- 第14回 CPS時代に組込みシステム開発に求められることとは
- 第15回 次世代車のE/Eアーキテクチャに見る組込みの進む道
- 第16回 RISC-Vが拓く専用プロセッサの時代
- 第17回 振動計測の大進化で、熟練エンジニアのスキルを広く身近に
-
- 零の巻:組込みというお仕事
- 壱の巻:2進数と16進数を覚えよう!
- 弐の巻:割り込みとポーリング
- 参の巻:printf()が使えない?
- 四の巻:これにもIntelが入ってるの?
- 五の巻:Endianってなに?
- 六の巻:マルチタスクとは
- 七の巻:スタックってなあに?(1)
- 七の巻:スタックってなあに?(2)
- 八の巻:メモリを壊してみましょう
- 九の巻:コードが消える?~最適化の罠~
- 拾の巻:例外が発生しました
- 拾壱の巻:コードサイズを聞かれたら
- 拾弐の巻:キャッシュは諸刃の剣
- 拾参の巻:デバイスにアクセスするには
- 拾四の巻:セキュリティってなに?(1)
- 拾四の巻:セキュリティってなに?(2)
- 拾四の巻:セキュリティってなに?(3)
- 拾五の巻 :DMA対応と言われたら(1)
- 拾五の巻 :DMA対応と言われたら(2)
- 拾六の巻:ヒープとスタック
- 拾七の巻:フラグメンテーション
- 拾八の巻:CPU起動とブートローダ
- 拾九の巻:kmとKByteの「kとK」
- ビリーへの質問:DMAとキャッシュの関係
- ビリーへの質問:スタックオーバーフローについて
- ビリーへの質問:CPUレジスタについて