組み込みソフトウェア、とりわけリアルタイム組み込みソフトウェアの開発に携わるソフトウェアエンジニアは、コードの有効性と効率を改善するための創意工夫に熱心です。本稿では、コンパイラを使いこなしてリアルタイムシステム上でのデバッグ作業をなるべく軽減するという、重要でありながら疎かにされがちなアプローチについて紹介します。

エラーと警告

まず、正しく動作させたいファイルからのコンパイラエラーと警告だけを除去すべきです。多くのエンジニアは、コンパイラエラーと警告を自分のコーディングスタイルや手腕に対する批判であるかのように受け止めてしまいます。

実際、コンパイラからのメッセージはどれも、「あなたの書いたコードはどう解釈して良いのやら定かではない」とか、ひどい場合には「あなたの書いたコードは意味を成さない」といった内容です。これでは即刻レッドカードを突きつけられたかのように感じても仕方ないかもしれません。しかし、コンパイラがコードを正しく解釈できなければ、生成されたコードが思い通りに動作しない可能性は非常に高くなります。コードをリアルタイム動作させる段階でコーディングエラーを見つけるよりも、コンパイラが正しく解釈できるよう最初から配慮してコードを書き、あの忌々しいコーディングエラーと付き合う方が遙かに容易です。

図1:Microchip TechnologyのMPLAB XCコンパイラはエラーおよび警告メッセージを豊富に提供します

しばしばエンジニアはリアルタイムデバッグに時間をかける事を嫌いますが、彼らはクリーンなコンパイルこそが何よりも必要である事を認識していません。

クリーンなコンパイルを心掛けるようアドバイスすると、「でも時間がもっとかかるでしょう。僕には時間が足りないんです!」というのが典型的な反応です。コンパイラエラーと警告の除去は退屈な作業ですが、ボード上でリアルタイムコードの不正挙動が見つかるよりはずっとマシです。コードが期待通りに動作しないために上司にあれこれ言われ、夜遅くまで残業してデバッグに骨を折るのは辛いものです。

巨大なコードを一気に書いてクリーンなコンパイルを目指せば、エラーばかりでイライラするのは当然です。コンパイラとの格闘なしでもリアルタイムコードの作成は大変な作業なのです。コードの一部を書いては頻繁にコンパイルし、コンパイラが正しくコードを解釈できている事をそのつど確かめる事を推奨します。

あるいは、コードの頭で見つかった最初の1つか2つのエラーだけを除去しては再コンパイルする方法でも時間を節約できます。たった1つのセミコロンが抜けていただけで、コンパイラが印刷を止めるほど大量のエラーメッセージが発生する事もあるのですから。

図2:Microchip製MPLAB XC32コンパイラが表示した重要警告メッセージ。このコードは予期した通りには動作しないでしょう

リアルタイム動作のための確実なコーディング手法

誰でも知っている事ですが、コーディングに際しては - 単一用途の簡潔な関数、明瞭な制御フロー、データの適切なカプセル化(グローバル変数の使用を極力控える)、整合性が取れたポインタ/配列の使用 - を心掛けるべきです。これにより、リアルタイムコードの開発を大幅に効率良く進める事ができます。コードの改善が大変と感じているならば、MISRAチェッカーを使うべきです。MISRAはC言語で頻繁に問題を生じる用法を制限する一連のルールを備えています。MISRAに忠実に従えば、より確実な動作が得られてコードが複雑怪奇になる事も防げます。クリーンなコーディングを実践すると、コードサイズも驚くほど小さくなるでしょう。

最適化

リアルタイムに限らず組み込みプログラミングにおいては「最適化」こそが最大の悩みの種です。以下では、最適化にまつわる問題を解消するためのいくつかのヒントを紹介します。

アルゴリズムの選択

いかなる場合も、コンパイラによる最適化がアルゴリズム自体の最適化に勝る事はありません。コンパイラの役割は「効率的」なコードではなく「正確な」コードを生成する事です。オプティマイザは、コンパイル後にコードを「掃除」しているに過ぎません。多くのエンジニアは、自分が書いたコードをオプティマイザが変更してくれるのだと勘違いしています。しかし決してそうではありません。

インラインアセンブリを使わない

リアルタイム動作が要求される場合、多くのエンジニアはインラインアセンブリを使わなくてはいけないと考えます。しかし、まともに動かすのが先であり、高速化を図るのはその次です。このアプローチを取るには、インラインアセンブリを使わずにすべてのコードをC言語で書くべきです。しかし「インラインアセンブリは良くないから使うな」と言うのではありません。「正確に書くのが難しく、通常は使わなくても済むので使うのを避けるべきだ」と言っているのです。コンパイラが提供するビルトイン関数の使用を推奨します。どうしてもインラインアセンブリが必要だと言う場合でも、「extended」書式をよく理解した上でその使用を検討すべきです。これにより、コードの用法をコンパイラに明確に伝え、直接のレジスタ名ではなく「C」変数を使う事ができます。

最適化による「無意味な」コードの削除はリアルタイムコードを破壊する

オプティマイザは書かれたコードを変更するわけではありません。では、オプティマイザはどのようにしてコードサイズを削減するのでしょうか。その答えの1つは「無意味な」コードにあります。コンパイラは、コードを削減するための1つの手がかりとして「無意味な」コードを探します。このため、コンパイラが何をもって「無意味」と判断するのかを理解する事が重要です。下記のタイミングループを例に挙げます。

for(i = 0; i < 10000; i++); // wait one microsecond

このコードを書いたエンジニアにとって、この1μsの待機は重要です。しかし、コンパイラはその重要性を認識できません。このため、オプティマイザはこの命令文全体を削除してしまいます。なぜならば、このコードは「無意味(効果を持たない)」だからです。コンパイラによってタイミングループが削除されてしまった経験を持つエンジニアであれば、これによってコードの動作に生じる問題を誰でも身に染みて理解しているでしょう。

最適化における一般的原則として、コンパイラが「この表現内の値は使われず、副作用も生じない」と判断すれば、そのコードを「無意味な表現」として削除してしまいます。このような最適化にまつわる問題を回避する方法はいくつかあります。その1つとしてvolatile 修飾子について説明します。

共有変数、volatile修飾子、割り込みなしのアクセス

割り込みハンドラなどの並列動作はリアルタイムプログラミングにおいて非常に重要です。しばしばISRとの間で変数を共有する必要があります。共有された変数は最適化の際に頻繁に問題を生じます。なぜなら、コンパイラは原則として変数の変化をすべて確認できると見なすからです。並列実行イベント間で共有される変数を保護するためには、修飾子「volatile」が便利です。volatileはコンパイラに「君はこの変数の変化を知る事はできない」と知らせるのです。その結果、オプティマイザはその変数へのアクセスに関連する最適化を一切実施しません。

ただしvolatile は、アクセスが割り込まれない(アトミック、1ステップで実行される)という事を意味するのではありません。例として、メインラインコード内の単純な代入式「volatile long x = 3;」を見てみましょう。これはアトミックであるように見えますが、ISR内の同様の代入によって問題を生じます。8ビットコンパイラは、4回連続の8ビット書き込みで32ビットの「long」書き込みを構成するでしょう。これら4回の書き込み中に割り込みISRが"x"に書き込むと、制御がメインラインに戻った時点で"x"の値が化けます。これは、リアルタイムコードで発生する不可解なバグにありがちな原因です。

共有された変数のアクセスを効果的に制御する1つの方法は、アクセサ関数を使う事です。アクセサ関数が提供する共通コードを使うと、各変数に必要に応じた保護レベルを定義し、アクセスを効果的に規制できます。前述の例の場合、"x"への書き込み中は割り込みを抑止する事ができます。このように、アトミック性を確実にする必要がある場合、volatile の代わりにアクセサ関数を使うべきです。

拘束されないポインタのキャスティング(型変換または型パンニング)

キャスティングは、ポインタを使う際、型システムの問題を回避するための方法ですが、リアルタイムコードをめちゃくちゃにする方法でもあります。標準構文に従えば、キャスティングポインタは構文的には許容されますが、しばしば無意味です。正しく機能しません。

一見、下記IEEE 754浮動小数値の指数演算表現を効率的に実行できるように思えます(実際、特定のアーキテクチャで特定の最適化レベルにおいては正しく機能します)。

float x = 186000;
int exponent = ((*(int*)(&x)) & 0x7F800000) >> 23;

しかし、この種の表現は脆弱であり、コンパイラや最適化の設定によっては予期せぬ結果が生じます。従って、キャスティング ポインタの代わりに、下記のような共用体を使うべきです。

union {
 float f;
 struct float_format s;
} v;

値が v.f に代入されると、構造体を介して指数または他の任意のフィールドに容易にアクセスできます。最適化の際も、コンパイラはアクセスを効率的かつ安全にできます。

Microchip製MPLAB XCのような最新のコンパイラを使うと、短期間でリアルタイムシステムのコーディングが可能です。他のツールと同様、これらのツールも特徴をよく理解して上手に使いこなす必要がありますがMPLAB XCコンパイラは習熟が容易です。本稿をお読み頂いた今、コンパイラと最適化およびリアルタイム コードの作成にまつわるイライラと頭痛から解放されるはずです。

著者プロフィール

Joe Drzewiecki
Microchip Technology
Senior Manager of Compilers & Software Development