スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

可変個引数マクロを使う

この世で変わらないのは、変わるということだけだ。
スウィフト

C99規格(ISO/IEC 9899:1999 - Programming Language C)は1999年12月にISOによって規格化されたC言語の新しい規格です。従来のC言語(C89やANSI C)には含まれていない新機能が数多く規格として導入されています。以前の記事「bool型の落とし穴」で触れた_Bool型もC99の新機能の一つです。

今回、C99の新機能の中で個人的にかなり便利だと思っている「可変個引数(可変個数引数)マクロ__VA_ARGS__」と「定義済みマクロ__func__」を紹介します。

拡張マクロ機能の使用例

最近のデバッガは高機能化しているとはいえ、ログ出力(ロギング)によるデバッグが便利なケースはまだまだあります。拡張マクロ機能はこのようなログ出力の記述時に、力を発揮します。
例えば、次のようなログ出力関数があったとします。
void debug_printf(fmt, ...);
可変引数関数は従来のC言語からサポートされています。関数本体の記述方法は関連リンク(Manpage of stdarg等)を参照して下さい。

さて、このログ出力関数はデバッグ関数のため、できればデバッグ中以外は完全に無効化してしまいたいと考えます。そこで、次のようなマクロを定義しようとします。
#if defined(DEBUG)
#define DEBUG_PRINTF(fmt, ...) debug_printf(fmt, ...)
#else
#define DEBUG_PRINTF(fmt, ...)
#endif
しかし、このような可変個数の引数を持つマクロは従来定義できなかったため、このコードはコンパイルエラーになります。C99ではこのような可変個数マクロがサポートされるようになりました。C99での記述は上記とは若干違い__VA_ARGS__マクロを使用します。
#if defined(DEBUG)
#define DEBUG_PRINTF(fmt, ...) debug_printf(fmt, __VA_ARGS__)
#else
#define DEBUG_PRINTF(fmt, ...)
#endif
これで、先程の目的は達成されました。
次に、このマクロにもう少し手を加えてみましょう。ログ出力はエラー検出時に出力されることが多いと思いますので、出力コードのあるソースファイルの場所を一緒に出力しておくと何かと便利です。例えば、次のようになります。
#if defined(DEBUG)
#define DEBUG_PRINTF(fmt, ...) \
  printf("%s:%d\n", __FILE__, __LINE__); \
  debug_printf(fmt, __VA_ARGS__)
#else
#define DEBUG_PRINTF(fmt, ...)
#endif
これで、ログ出力のあるファイル名と行数が一緒に出力されるため、エラー発生箇所の特定が容易になります。しかし、実際は、ソースの行数よりも関数名の方を知りたいことの方が多いでしょう。C99で導入された定義済みマクロ__func__がこの要求に応えてくれます。
#if defined(DEBUG)
#define DEBUG_PRINTF(fmt, ...) \
  printf("%s:%d:%s\n", __FILE__, __LINE__, __func__); \
  debug_printf(fmt, __VA_ARGS__)
#else
#define DEBUG_PRINTF(fmt, ...)
#endif
これで、関数名も出力されるようになりましたので、エラー発生箇所の識別が容易になります。

拡張マクロ機能のサポート状況

ここで紹介したC99の新機能(__VA_ARGS_と__func__)は、gccではサポートされていますが、VC++.NETではサポートされていないようです(その他のC99機能に関しても、VC++ではほとんどサポートされていません)。 非常に便利な機能なので、使用するに値すると思っていますが、移植性は低くなるということは覚えておく必要があります。移植性を考えれば、今回あげた例のようなデバッグコードのみの使用等に限定した方が良いでしょう。
# マクロなのでそもそも使いすぎない方が良いですが(^^;)

【関連記事】
printfとtypedefの微妙な関係 - 無視されがち?なformt warning
bool型の落とし穴

【関連リンク】
Manpage of STDARG
プログラミング言語Cの新機能(seclanのほえほえルーム)
定義済みマクロ__func__と可変長引き数マクロ(フリーウェア徹底活用講座)

【関連書籍】
GCC GNU C Compiler―Manual & Reference 遠藤 俊徳
GCC: The Complete Reference Arthur Griffith
新Visual C++ .NET入門 シニア編 林 晴比古
スポンサーサイト

デバッグと不確定性原理

客観的事実など存在しない。
あるのは自分の目を通して見た事実だけである
ハイゼンベルク

プログラムの動作がおかしい。そこで、デバッグのためにログ出力のコードを仕込んで動作させてみる。すると、さっきまで調子の悪かったプログラムが正常に動いている。プログラムをもとに戻すとやっぱり動作がおかしい。

このような経験はないでしょうか。いわゆる、デバッグにおける「不確定性原理」の一例です。

不確定性原理とは何か

ハイゼンベルクの不確定性原理(uncertainty principle)とは量子力学における基本原理ですが、これはデバッグに当てはまります。オリジナルの不確定性原理の概要をWikipediaの説明から引用しておきます。
極微の世界では粒子の運動量と位置を同時に決定することはできないという原理。位置をより正確に観測する為にはより正確に「見る」必要があるが、極微の世界でより正確に見る為には、波長の短い光が必要であり、波長の短い光はエネルギーが大きいので観測対象へ与える影響が大きくなる為、観測対象の運動量へ影響を与えてしまうからである。位置を正確に測ろうとするほど、対象の運動量が正確に測れなくなり、運動量を正確に測ろうとすれば逆に位置があいまいになってしまい、両者を完全に正確に測る事は絶対に出来ない。
冒頭のデバッグの例は、まさにこのような不確定性原理がデバッグに適用されたような形になっています。つまり、ログ出力というプログラミングに対する観測行為が、プログラムの動作に影響を与えてしまったために、もともとの状態が変化してしまっていたのです。バグが再現しなくなったのは、観測行為がプログラムの動作を変えてしまっていたのです。

では、なぜログ出力がプログラムの動作を変えてしまったのでしょうか?ログ出力を追加しただけで、アルゴリズムを変えたわけではありません。このような場合で最も多いのは、もともとのバグがタイミングに依存したものであった場合です。マルチスレッドや割り込み処理を行うようなプログラムの場合、ログ出力の追加による僅かなタイミングのずれが、バグを起こらなくしてしまうということは良くある話です。特に、ログの出力先がファイルやシリアル等の場合は、そのタイミングで実行タスクがI/O待ちでスリープしてしまうことがあるためよく起こります。
その他、コードの追加はメモリマップを変更してしまうため、メモリ破壊や変数の初期化漏れ等のバグの挙動を変えてしまうこともあります。

なお、ログ出力等のコード追加をしなくとも、デバッグのためにコンパイルの最適化レベルを変更したり、デバッガをつないだりするだけでもプログラムの挙動に影響を与えてしまうことがありますし、デバッガでブレークをかけたりする行為もプログラムの挙動に影響を与えてしまいます。

不確定性原理とうまく付き合う

では、この不確定性原理の影響を排除するには、どのようにすれば良いのでしょうか。残念ながら、これは「原理」であるので、完全に回避することは不可能です。しかし、何も悲観的になる必要はありません。見方を変えれば、これはデバッグのための観測についての指針を示してくれているものと考えられます。

オリジナルの不確定性原理の説明を再び引用すると、
位置を正確に測ろうとするほど、対象の運動量が正確に測れなくなり、運動量を正確に測ろうとすれば逆に位置があいまいになってしまい、両者を完全に正確に測る事は絶対に出来ない。
とあります。
デバッグに置き換えれば、正確な内部情報を知ろうとすればするほど、もとの動作に影響を与えてしまうということになるでしょうか。
ここで、見方を変えれば、
  • 動作に影響を与えないようにデバッグするためには、
    極力観測による影響が少なくなるようにすれば良い
と考えられます。

具体的な例をあげると、
  • ロギングを使用する場合、目的のログだけを出力する等、対象を絞れる仕組みを入れる
  • ログ出力のI/O負荷が問題になりそうな場合は、ログを絞ってメモリ上に残す
  • ブレークによる一時停止が影響しそうな場合は、条件ブレークを活用して最小限にブレークがかかるようにする。
    もしくは、ロギングを活用したデバッグを行う
等が考えられます。

不確定性原理によるリスク

冒頭の例でもそうですが、デバッグ用のコードの追加がバグを隠してしまうことがあります。このため、開発の終盤にはじめてデバッグ用のコードを抜いたり、最適化レベルを変更したりすると、とたんにプログラムが動かなくなる可能性があります。このような事を防ぐために、普段からデバッグコードを抜いたり、最適化レベルを変えても正常に動くかどうかをチェックしながら開発を進めることが必要です。

4873115930 【関連リンク】
不確定性原理 (Wikipedia)

【関連書籍】
デバッグルール 9つの原則、54のヒント
不確定性原理―運命への挑戦 (都筑 卓司)

マルチスレッドで書くべきか

マルチスレッドにするとソフトは速くなるのか?(笑門来福)

最近では「ハイパースレッディング」や「マルチコア」というキーワードが「高速化」というキーワードとともに登場する機会が多いので、そのような誤解が一部にはあるのかもしれません。分かっている人にとっては当たり前の話ですが、「マルチスレッド=高速化」ではありません。

マルチスレッド処理あるいはマルチプロセス処理とは、複数のスレッドもしくはプロセスを並行して動作させることによって、複数の処理を行なうことです。並行処理といっても、単一CPUのシステムでは、実際の処理はシーケンシャルに実行されるため、CPU処理は全く高速化されません。それどころか、マルチスレッド動作にはそれなりオーバーヘッドが伴うため、CPUの計算量だけとればむしろ増大する方向です。

では、なぜマルチスレッドプログラミングを行うのでしょう。ひとつは、I/O等の待ち時間の間に他のスレッドの実行を行うことで処理効率やレスポンス性を改善するためです。シングルスレッドでも書けないことはないのですが、スイッチのタイミングを自分で意識して書く必要があり大変です。このような処理はOSにまかせるべきですし、何より可読性・保守性があがります。
また、マルチコアになれば、本当の意味での並列実効で処理効率があがる可能性もあります。

但し、マルチスレッドには同期や排他等の問題がありますので、プログラミングする上での注意点もたくさんあります。マルチスレッド(マルチタスク)のプログラムというのは、組み込み系では日常的ですが、全く書いたことのない方もおられるでしょう。このあたりの話はまた別の機会に。

【関連書籍】
実践マルチスレッドプログラミング
Javaスレッド完全制覇
Pスレッドプログラミング

省コメントのススメ

コンピュータが理解できるコードなら誰でも書ける。優れたプログラマは人間が理解できるコードを書く。
よく「コメントをきちんと書け」ということを言われます。そのためか、初心者の書いたソースを見ると、必要以上に大量のコメントが書かれていることがあります。確かに全くコメントのないプログラムには読みにくいものが多いのは確かですが、何でもかんでもコメントをつけるのも同じく間違っています。余分なコメントは、プログラムとコメントの不一致、いわゆる「嘘のコメント」の原因になります。要するに「必要十分」なコメントを書くことが大切なのです。

といっても、どのようなコメントが「必要十分」なコメントなのかということに関して決まったルールがあるわけではありません。よく言われるものとしては、
  • if, while等の条件分岐箇所にコメントをつける
  • 関数の先頭に、引数や戻り値、処理内容のコメントをつける
  • 外部制限による注意を書く(HW制御手順等)
  • 一連のアルゴリズムの説明を書く(リスト中を二分探索するとか、ユークリッドの互除法は最大公約数を計算する等)
というあたりです。
しかし、これらに関してもソースコードを見てすぐに理解できる範囲であればコメントを書く必要はありません。プログラムの情報を補うのがコメントの目的ですから、補うべき情報がなければ書く必要はないのです。見方を変えると、コメントを付け加える前に、ソースの変更によってコメントを書かなくても分かるようにできないかと考えるべきです。 次に例をあげます。
bool flag = false; /*全タスクが終了しているかを示すフラグ*/

/* 全てのタスクが終了するまでループ */
while (!flag) {
  /* 各タスク状態のチェック */
}
この例の場合、コメントがないと、while分からは「何らかのflagがfalseの間はループしている」ということしか伝わらりません。よって、コメントはプログラムを補っているということで必要性がありそうに思えます。
しかし、落ち着いてよく考えてみると、そもそも「flag」という何の変哲もない変数名をつけたことがまずいのではないでしょうか。よって、次のように書いていれば、コメント無しでも十分意味は伝わります。
bool all_task_finished = false;

while (!all_task_finished) {
  /* 各タスク状態のチェック */
}
もうひとつ例をあげます。
/* 2つの矩形エリアrect1とrect2にx,yが含まれる場合*/
if ((x >= rect1.x1 && x <= rect1.x2
     && y >= rect1.y1 && y <= rect1.y2)
     && (x >= rect2.x1 && x <= rect2.x2
         && y >= rect2.y1 && y <= rect2.y2)) {
  /*処理*/
}
このままでは、条件式が長いのでコメントが必要になっています。これも、
/* 2つの矩形エリアrect1とrect2にx,yが含まれる場合*/
bool is_in_rect1 = (x >= rect1.x1 && x <= rect1.x2
                    && y >= rect1.y1 && y <= rect1.y2);
bool is_in_rect2 = (x >= rect2.x1 && x <= rect2.x2
                    && y >= rect2.y1 && y <= rect2.y2);
if (is_in_rect1 && is_in_rect2) {
  /*処理*/
}
とすることでコメント無しでも意味が伝わりやすくなります。このように、関数分割しなくとも、処理結果を適切な名前の変数に代入するという方法で、可読性をあげるのも一つの手法です。

なお、これらの例の場合は、無理にコメントをなくさなくとも良いとは思います。重要なのは、極力コメントを書かないという点ではなく、コメント付与の前に、コード側に問題がないかを考えることなのです。コメントを付与することは、情報をコードとコメントの二箇所に分散させることになるため、一貫性を保つ必要性が出てしまうということを忘れてはいけません。

なお、関数の仕様等はコメントとして書いた方がよいと思いますが、この場合も、doxygenjavadocの形式で記述することで、仕様書とコメントの両方に情報が分散することを防ぐことができます。情報の分散、二重管理は何かと問題を引き起こすので、しないで済む分はしない方が無難です。私がハンガリアン記法が好きになれないのも型情報が二重管理になっている点です。

最後にまとめるとすると、
  • コメントを必要最小限に
  • コメントが必要そうだと感じた場合は、プログラム側に問題がないかと疑う
ということです。

【関連記事】
英語のコメント
ドキュメントの自動生成
変数の命名規則

【関連リンク】
コメント論悪態のプログラマ
実効的コメント書方Cプログラミングの秘訣
doxygenを使おう

【関連書籍】
文芸的プログラミング Donald E. Knuth
リファクタリング―プログラムの体質改善テクニック
プログラミング作法

ピアノマン

イギリスに現れた記憶喪失の天才ピアニスト、ピアノマン。一時期各種メディアで取立てられていましたが、最近では関連記事はほとんど見かけることはなくなりました。さて、そのピアノマン報道の中で、よくBGMとして使われていたのがビリー・ジョエル(Billy Joel)の名曲ピアノマン(PIANO MAN)でした。この曲は非常に大好きな曲なので紹介しておこうと思います。

B0000CD87J「ピアノマン」は私の中では、10本の指には入るくらい大好きな曲です。学生時代に買ったベストアルバム(ビリー・ザ・ベスト)の一曲目がPIANO MANで、初めて聞いた時からはまってしまいました。何がそんなに好きなのかと言えば、その雰囲気です。理屈抜きにとにかく雰囲気のある曲なのです。演奏も歌もメロディラインも。歌詞も大好きです。
It's nine o'clock on a Saturday
The regular crowd shuffles in
There's an old man sitting next to me
Making love to his tonic and gin

He says, "Son can you play me a memory
I'm not really sure how it goes
But it's sad and it's sweet
And I knew it complete
When I wore a younger man's clothes"

Sing us a song you're the piano man
Sing us a song tonight
Well we're all in the mood for a melody
And you've got us feeling alright
静かな所で目をつむって聞いていると、アメリカのバーの喧騒の中に自分がいて、ビールの香りまで漂ってくるような気分になります。

ベリーベストビリー・ジョエルと言えば、「素顔のままで(Just The Way You Are)」「さよならハリウッド(Say Goodbye To Hollywood)」「ガラスのニューヨーク(You May Be Right)」「ニューヨークの想い(New York State Of Mind)」「マイライフ(My Life)」「アレンタウン(Allentown)」「オネスティ(Honesty)」「ストレンジャー(The Stranger)」と他のもよい曲がたくさんあります。最近(といっても半年以上前ですが)新しいベストアルバム(ピアノ・マン:ザ・ヴェリー・ベスト・オブ・ビリー・ジョエル)をリリースして、まだまだ現役で活躍していますが、やはり70年代、80年代の全盛期の曲が最高ですね。

【関連リンク】
Billy Joel Official Site
ソニーミュージックの洋楽ポータルサイト(piano manも少しですが試聴できます)
ピアノマン - 沈黙する謎の天才ピアニスト 英 (X51.ORG)

続きを読む

bool型の落とし穴

奇蹟は教理の真偽を見わけ、教理は奇蹟の真偽を見わける。
パスカル
C/C++でbool型(ブール型/boolean型)を使用する時のお話です。

C言語は言語レベルでbool型(ブール型)をもっていません。しかし、プログラミング上、bool型を定義した方が可読性が良くなることもありますので、独自にtypedefでbool型を定義して使用されている方も多いと思います。C99(1999年に制定されたC言語の第2版)ではstdbool.hが導入され、その中で_Boolが定義されていますが、これが実際に使用されているのはあまり見かけません(そもそもC99仕様をフルに活用したコードを見かけることは少ないのですが)。 今回はこのように独自定義されたbool型、及び、処理系によって用意されたbool型を用いる時の注意点について説明します。

TRUE,FALSEの値

bool型を独自定義する場合、通常TRUEとFALSEもあわせて定義します。この時問題となるのが、それらにどのような値を持たせるかという点です。大抵はTRUEが1、FALSEが0となるようにマクロ定義する、あるいはenum型でbool型を定義するというケースがほとんどでしょう。==演算子や!=演算子が返す値にあわせれば自然とそうなります。こうすることで次のような書き方が可能になります。
if (is_valid(&pos)) {
  /*必要な処理*/
}
bool型を返す関数をそのまま条件式として使用できます(※1)。しかし、TRUE,FALSEの値はこれ以外の値で定義されていることもあり得ます。

例えば、POSIX系の関数(open/read/write等)は成功時に0または正数を返し、エラーで-1を返すものがあります。strcmp()やmemcmp()は、内容が等しいときに0を返します。そう考えるとTRUEが0というのもありかなという人がいても不思議ではありません。

個人的には最初に書いたTRUEが1、FALSEが0が良いと思いますが、他人のソースの場合は、念のため確認した方が賢明です。 なお、関連リンクにもありますが、マクロ定義がかえって話を複雑にしては本末転倒ですので、TRUE,FALSEは使わず、1や0の直値を使うというのも一つの手です。

BOOL型のサイズ

BOOL型のサイズ(sizeof(BOOL))も悩みの種です。C99で導入された_Boolでも、「型_Boolとして宣言されたオブジェクトは, 値0及び1を格納するのに十分な大きさをもつ」とされているだけで、実際にどのようなサイズになるかは処理系依存となっています。BOOLの二値を表現するには1byteでも十分なのですが、処理効率を重視してintを同じサイズ(4byte)で定義されていることもあります。これは、C99の_Boolだけに言える事ではなく、C++のbool等でも言えることです。例えば、Visual C++では、5.0以下ではintと同じ(4byte)ですが、最近は1byteです。

そのため、これらのbool型を用いたソースを移植する際には、次のような影響が出ることがあります。
  • 構造体のアライメントがかわる。
    場合によっては、移植後にアライメントエラーを起こす可能性もある
  • BOOL型変数が多く使われている場合、使用メモリサイズに影響する。
    1byteと4byteでは4倍!
そのため、_Boolやbool等の処理系依存の型をそのまま用いている場合は、移植の際にはbool型のサイズを確認した方が良いでしょう。

以上は、処理系によって用意されたBOOLについても注意ですが、独自定義したBOOLについてもサイズに関しては注意が必要です。例えば次のソースを見て下さい。
void TOM_check_flag(TOM_BOOL* p_flag);
void BOB_check_flag(BOB_BOOL* p_flag);
void JON_check_flag(JON_BOOL* p_flag);

check_all_flags(
{
  MY_BOOL flag[3];
  
  TOM_check_flag(&flag[0]);
  BOB_check_flag(&flag[1]);
  JON_check_flag(&flag[2]);

  /* flagを見る処理 */

}
少々不自然で、あまりいい例ではありませんが(^^;)。 このソースでは、TOM,BOB,JONのcheck_flag関数で何かのflag値を取得しています。このコードの問題点は、MY_BOOL型と、TOM_BOOL,BOB_BOOL,JON_BOOLがすべて同じサイズであるという前提で書かれています。しかし、MY_BOOLが1byte、その他のXXX_BOOLが4byteの場合どうなるでしょうか?XXX_check_flagの方では、引数のポインタを4byte変数へのポインタとして扱いますので、思わぬメモリ破壊をおこす可能性があります。
なお、このソースの場合は、キャストが入っていないのでコンパイル時にwarningが出て気がつくとは思いますが、十分注意が必要です。

二重定義の注意

BOOL型は独自定義されることが多いので、BOOLやbool、boolean等のありきたりな名前で独自定義して使っていると、外部ヘッダをインクルードした時に二重宣言エラー(previous declaration of ...)が出ることがあります。 この辺りは、コーディング規約で、型名の頭にプレフィックスをつける等の対策がなされていれば発生しません。起こっても置換すれば済む話ではありますが、気をつけましょう。 BOOL以外でも、INTやCHAR,BYTE等もよく被る気がします。

Javaのbooleanのように最初から言語機能として組み込まれ、独立した型として強く型付けされていればもっと使いやすいのですが。ないものねだりをしてもしようがないので、boolは注意して使いましょう。

(※1)比較式を省略することについては賛否両論あるかと思いますが、私は分かりやすいと思っているので、bool型を返す関数ではこのような書き方をしてます。

【関連書籍】
改訂版 組込みソフトウェア開発向けコーディング作法ガイド[C言語] (SEC BOOKS)
Cプログラミングの落とし穴 A.コーニグ
GCC GNU C Compiler―Manual & Reference 遠藤 俊徳
プログラミング言語C ANSI規格準拠 B.W.カーニハン D.M.リッチー

【関連リンク】
C99あれこれ ~_Bool編~ (闘わないプログラマ)
直接数値を書いても悪くはない場合(フィンローダのあっぱれご意見番)

ゲーデル・不完全性定理

最近読んだ本を紹介します。

ゲーデル・不完全性定理―理性の限界の発見 ゲーデル・不完全性定理―"理性の限界"の発見
吉永 良正

大学生の頃に買った本ですが、家を片付けている時にたまたま目について読み返してみました。
不完全性定理については、名前は聞いたことはあるけども、具体的な内容については知らない方も多いと思います。本書は、「はじめに」に、『この定理の証明のアイデア自体は、いたってシンプルで明快なものであり、きちんと説明すれば中学生でも理解できるものなのです。本書では実際に、そのような説明を試みてみました。』とあるように、馴染みやすいパラドックスの例を紹介しながら、不完全性定理にいたる説明がされています。しかし、実際はそんなに平易な内容とは言えません。さらに、アマゾンのレビューを見ると、ところどころ間違った表現も散見されるそうですので、今から不完全性定理に触れてみたいといかたは別の本(例えば、ゲーデルの哲学―不完全性定理と神の存在論 (著者:高橋 昌一郎))の方が良いかもしれません。

本書では、嘘つきのパラドックス(クレタ人のパラドックス、エピメニデスのパラドックス)からラッセルのパラドックス、リシャールのパラドックス等多くのパラドックスが紹介されており、この点だけも結構楽しめます。そういえば、チューリングマシンの停止問題もひとつのパラドックスですよね。
パラドックスというのは人を惹きつけるものがありますね。

【関連リンク】
エピメニデスのパラドックスの真の解法
チューリングマシンの停止問題 (Wikipedia)

デバッグ指向のススメ

予めおもんぱかれば、簡単であるが、後になっておもんぱかれば、複雑になる。
ゲーテ -『格言と反省
デバッグ指向プログラミング(debug-oriented programming)とは何か。一言で言うとすると、「デバッグしやすいプログラムを書きましょう」ということです。テスト指向(test-oriented)と近いところもありますが、よりデバッグの効率に重点を置いた考え方です。詳しい内容は今回だけでは書ききれませんが、とりあえず今回は、なぜデバッグ指向なのか、デバッグ指向とは具体的にどんなものか、ということについて書いてみたいと思います。

バグの発生は必然

バグの発生は予定外の出来事ではありません。よほど小規模のプログラムでない限り、ソースコードが一発で不具合なく動くことはほとんどありません。つまり、デバッグ作業というものは、ソフトウェア開発において重要なステップなのです。実際、大抵のソフトウェア開発では、コーディングそのものよりもデバッグに費やす時間の方が多いことが多いのではないでしょうか。つまり、このデバッグをいかに効率よく進められるかということが、ソフトウェア開発の効率を左右するといっても過言ではありません。

デバッグ効率をいかにあげるか

では、デバッグの効率はどのようにすれば改善できるのでしょうか?項目としてあげるとすると、
(1)バグのパターンを知り、解析・切り分け方法を習得する
(2)デバッガ・各種ツールを使いこなす
(3)デバッグ用のコードを埋め込んでおく
(4)デバッグしやすいコードを書く
といったところでしょうか。

(1)についてですが、設計手法と同様、デバッグ手法にもパターンや系統的手法が存在します。よくあるバグのパターンを知っていると、デバッグ効率もあがります。
(2)についてですが、最近のデバッガはかなり強力です。メモリリークやオーバーランの検出に有用なツールのあります。デバッガやツールを使いこなせるかどうかでデバッグ効率は大きく変わってきます。

(3)(4)が、デバッグ指向プログラミングの勘所です。デバッグ用のコードといっても単にprintfを埋め込めというわけではありません。勿論ログを残すというのは有効なデバッグ方法ですが、それだけではなく、assertの埋め込み、デバッガ向けコード、デバッグ関数の埋め込み等があります。開発対象がライブラリであれば、ライブラリ自身だけでなく呼び出し側でのデバッグも考慮します。

細かい点は追々追加していきますので、今回はデバッグ指向の一例として、契約プログラミングについて説明します。

契約プログラミング

契約プログラミング(DBC:Design By Contract)という言葉をご存知でしょうか。プログラムのコード上に、満たすべき条件を埋め込んでおき、違反があれば検出するというものです。最も基本的なものにassertがあります。ご存知のように、条件をチェックして、不一致であればログ出力後、abortするものです。
#include <stdio.h>
#include <assert>

#define ENTRY_MAX 5
int g_entry[ENTRY_MAX]

int
nth_entry(int n)
{
  assert(n < ENTRY_MAX && n >= 0)
  return g_entry[n];
}

上記のコードでは、nが5以上で呼び出されると、abortしてプログラムが停止してしまいます。違反が検出された時点でプログラムが停止するため、デバッガで動作させていればすぐにバックトレースをとることもできます。

では、assertがなかった場合、どうなるでしょうか?運がよければ、不正メモリアクセスでsegmentation faultか何かで同じく停止し、assert使用時と同様に発生地点を発見できるかもしれません。しかし、大抵の場合、不定値を返したあと、しばらく実行が続いたのちにバグが表面化するため、一体どこでバグが入ったのかを調べるのが大変になります。バグは発生した瞬間に捕まえないと非常にやっかいなので、assertで異常を見つけた瞬間に停止させるのは非常に有効な手段です。

まれにつぎのようなコードをみかけます。
#include <stdio.h>
#include <assert>

#define ENTRY_MAX 5
int g_entry[ENTRY_MAX]

int
nth_entry(int n)
{
  if (n >= ENTRY_MAX || n < 0)
    return -1; /* out of range */
  return g_entry[n];
}

配列の範囲外アクセスの場合、エラー(-1)を返すということになっています。外部提供関数であれば、これもありかもしれませんが、これが内部関数であれば、エラーを返す意味がありません。内部ロジックがおかしいのですから、エラーなぞ返さずに異常検出でabortした方が望ましいのです。誰もエラーチェックしないような関数でエラーを返す意味はありません。先のassertを入れたコードのようにするべきでしょう。

assertは非常に有用です。必ず満たしていなければならない条件がある場合は、こまめにassertをいれるべきです。関数の引数だけでなく、複雑なアルゴリズムの計算経過の確認などでも使えます。assertはコメントと違い、直接コードとリンクするのでコードとの不一致を起こすこともありません。

但し、assertを入れるとそのチェックが入る分、コードサイズや処理時間に影響を及ぼします。ですので、数が多い場合は、段階ごとにassertをOFFにできるようしておいた方が良いでしょう。
#define DEBUG_LEVEL 0

#if DEBUG_LEVEL > 0
#define ASSERT_1(x) assert(x)
#else
#define ASSERT_1(x)
#endif

#if DEBUG_LEVEL > 1
#define ASSERT_2(x) assert(x)
#else
#define ASSERT_2(x)
#endif

こうすれば、DEBUG_LEVELマクロで、一部のassertだけを有効にできます。

今回は契約プログラミング,assertについて説明しました。その他のデバッグ指向プログラミングに関するTIPSも本サイトでおいおい紹介していきたいと思います。ちなみに、「デバッグ指向」という単語は私の造語ですので、Googleで調べていただいてもhitしません。あしからずご了承ください (^^;)

※ D言語ではより強化された契約プログラミングの機能が言語レベルで実装されているそうです

4873114063 【関連記事】
バグのパターン

【関連リンク】
Linuxのデバッグ手法をマスターする
プログラミング言語D - 契約プログラミング

【関連書籍】
.NET&Windowsプログラマのためのデバッグテクニック徹底解説
Javaデバッグ明快技法
D言語パーフェクトガイド―一歩先行くC/C++/C#/Java開発者に贈るD

プログラミング言語と思考

『現実の世界』というものは、多くの限度にまで、その集団の言語習慣の上に無意識的に形づくられているのである。
エドワード・サピア 『言語 - ことばの研究序説
サピア=ウォーフの仮説(The Sapir-Whorf Hypothesis)というものをご存知でしょうか。人が話す言語と、人の物事に対する考え方・理解の仕方には密接な関係があるという言語学分野における有名な仮説です。
有名な例として、虹の色数の話があります。日本では、7色が通例ですが、これは万国共通の認識ではなく、場所によっては8色であったり、6色であったりします。日本でも、緑色のことを「青」と呼ぶことがありますが、このように青と緑を区別しない言語圏では「青」と「緑」が区別されないので色数が減ることがあるそうです。
他の興味深い例としては、
  • オーストラリアのある民族は、紙に書かれた矢印を「上下左右」ではなく「東西南北」で認識する
  • 数字をあらわす言葉として1と2しか持たない(それ以上はたくさんになる)アマゾンに住むピラハー族は、数の違う電池、魚などが写っている画像を使う「マッチング課題」において、数が3以上になると物体が何であっても判定するのに苦労する
等という話があります。

サピア=ウォーフの仮説はあくまで「仮説」ですが、このような自然言語と思考との関係は、プログラミング言語と設計・分析プロセスとの関係についても言えるのではないでしょうか。プログラミング言語は実装の道具というだけではなく、設計・モデリングを行う時の思考の道具でもあります。要求仕様を聞いて頭に設計イメージが浮かべる時、C言語を主に使っていると考え方も手続き指向になり、Javaを主に使っていればオブジェクト指向になるのではないでしょうか。言語の使用経験の長い人ほど、すぐに実装イメージを思い浮かべるため、このような傾向がありそうです。

よくC言語の経験の長い人の場合、オブジェクト指向設計に慣れるのが難しいという話を耳にします。勿論、C言語でもオブジェクト指向的なプログラミングは可能ですが、言語仕様がオブジェクト指向ではないため、実装に多少のぎこちなさが出ます。C言語では手続き型で書いた方が自然なのです。

とはいえ、大規模開発においてオブジェクト指向設計は優れた方法論ですC言語のみのプロジェクトであっても、その考え方を導入する価値はあります。しかし、その理屈だけ頭に入れてもなかなか思考はかわってくれません。こんな時に思考を変えるには、まず言語を勉強するのが一番です。勿論、言語仕様を頭に入れるだけでは意味がありません。実際に使ってみる必要があります。

私のいる組み込み業界では、使用するプログラミング言語は多くの場合C言語です。できることなら、いろんな言語を使ってみたいのですが実際なかなかそうはいきません。慣れない言語をいきなり業務で使用するのも危険です。そこで、私の場合は、開発やテスト用のツール作成等、割合自由の利く部分で、普段使わないような言語を使うようにしています。

世の中にプログラミング言語はごまんとあります。コンパイラ言語にスクリプト(インタープリタ)言語。関数型に宣言型。オブジェクト指向にアスペクト思考。多くの言語に触れることで、思考の幅も広がり、柔軟な設計も可能になります。普段同じプログラミング言語ばかり使っている方は、是非ともいろんな言語を使ってみて下さい。
異文化交流は言語習得から、です(^^)

【関連リンク】
言語は思考を決定するのか?(スラッシュドット・ジャパン)
サピア=ウォーフの仮説 (ざつがく・どっと・こむ)
言語相対性理論 ~サピア=ウォーフの仮説~
サピア=ウォーフ仮説再考
虹の色数の話
プログラミング言語一覧(Wikipedia)

【関連書籍】
言語―ことばの研究序説 Edward Sapir
言語の相対性について
認知パターン―オブジェクト技術のための問題解決フレームワーク ロバート コニツァー他

間違えやすい日本語

間違っていながら、しかもそれを認めたがらないとき、人はついには怒り出す。
トーマス・ハリヴァーン
少し前の話になりますが、TBSで「全国日本語検定試験 日本語チャンピオン決定戦」という番組が放送されていました。内容は、間違えて使われやすい漢字や慣用句などが、2択形式で100問。これが意外と難しいのです。面白いので、いくつか紹介しておきます。

・「采配をふるう」は間違いで「采配をふる」が正しい
・「気の置けない相手」は「油断できない相手」という意味ではなく「気を使わなくていい相手」の意味
・「おまちどうさま」は間違いで、「おまちどおさま」が正しい
・「押しも押されぬ」は間違いで、「押しも押されもせぬ」が正しい
・「役不足」は「役目に対して力量が足りないこと」ではなく「力量に対し役目が軽すぎること」

私が間違って覚えていたものをあげさせて頂きましたが、みなさんは正しく使われていましたでしょうか?TBSのサイトから問題用紙と回答用紙がダウンロードできますので、番組をご覧にならなかった方もチャレンジできます。

関連リンクに挙げた、言葉の誤用をとりあげたサイト(言葉の誤用の小ネタ)や、漢字の雑学を扱ったサイト(新感覚!「楽しむ漢字」の辞典長訓読み選手権)も面白いです。一字で「あるきかたがただしくない」「ほねとかわとがはなれるおと」といった読みをもつ漢字や、84画の伝説の漢字「おとど」等(?)が紹介されています。

ちなみに、本記事のタイトル「間違えやすい日本語」に関しても、実は「間違いやすい日本語」ではないのか、というご意見もあるかもしれません。「間違う」と「間違える」の使いわけについては、単純に自動詞と他動詞として使いわけるわけにもいかないようです(下記関連リンク参照)。私自身は「うっかり誤用する」という場合には、「間違う」より「間違える」の方がしっくりくるということで、とりあえず「間違えやすい日本語」という形にしておきます。(^^;)

プログラミングにしても文章にしても、理論をこねくりまわすだけでは実力はつきません。書くことが実力向上への近道です。私は、まだまだ文章が苦手ですが、本ブログに記事を書くことで、少しずつでも文章力が向上すればな、と思っている今日この頃です。

【関連リンク】
日本語チャンピオン決定戦 '05 (TBS)
言葉の誤用の小ネタ (ひでゆきの小ネタ部屋)
新感覚!「楽しむ漢字」の辞典
長訓読み選手権
間違うと間違えるの意味と用法を考える(文法考察ファイル)
言葉の誤用 (翔ソフトウェア)

【関連書籍】
問題な日本語―どこがおかしい?何がおかしい?  北原 保雄
その日本語は間違いです―正しい言葉の使い方 神辺 四郎
かなり気がかりな日本語 野口 恵子

意図しないオープンソースの混入を防ぐ

最近はオープンソースを商用ソフトウェアや組み込みソフトウェア等にも積極的に取り入れられるようになってきました。実績のある既存コードを流用することは、開発コストを下げるということに関して非常に効果的です。
しかし、オープンソースを商用利用する際は、ライセンス等に留意する必要があります。GPLはその最たる例で、開示できないソースコードとGPLでライセンスされたソースコードをむやみに混在させると、全てのソースコードの公開が必要になってしまう可能性があります(※1)。

どのようなオープンソースをどこで使用しているかをあらかじめ知っていれば、管理も出来ます。安全のために、開発段階でオープンソースの流用をあきらめることもできます。
しかし、中には、各プログラマが安易な気持ちで、オープンソースの一部を非公開のソースコード中にコピー&ペーストしてしまっていることもあるかもしれません。このコピー元がGPLで、何かの拍子にそれが表面化すれば、ある日突然公開をせまられるという可能性もあります。

このような背景のもと、先日テンアートニという会社が、開発中のソフトウェアに対するオープンソースソフトウェア混入を検査するサービス「ProtexIP」の国内提供を7月中旬に開始すると発表しました。同サービスを米国で扱っている米Black Duck Softwareと共同で、国内企業向け提供を行うそうです。

どの程度の精度があるものか興味があります。

※1 GPLソースと直接リンクしていなければ、公開しなくとも良いという話もありますが、実際は公開・非公開の境目を厳密に定義するのは非常に難しい問題です。

【関連リンク】
テンアートニ、自社開発ソフトへのオープンソースソフト混入を検査するサービス(ZDNet)

magicdevが重い

4873111382 最近Linuxがなんだか重いので、topで調べてみました。
すると、magicdevというプロセスがやたらとCPUを使用しています。

magicdevというのは、CDやDVD等のリムーバブルメディアに対応した一種のauto mountデーモンのようなものらしいのですが、調べてみると同様の症状に陥っている人も散見されます。私は不要なのでアンインストールしました。
%> rpm -e magicdev

ちなみに私のディストリビューションはVine Linux 3.1です。
Redhat系のディストリビューションには標準で含まれているようです。

HDDに定期的にアクセスするようなので、HDDのアクセス音が気になる人はこれが原因かもしれないので、一度切ってみるといいかもしれません。

【関連書籍】
Fedora Core4ビギナーズバイブル 大津真 向井領治 まえだひさこ
UNIXシステム管理 第3版〈VOLUME 1〉 アイリーン・フリッシュ

比較広告

Vodafoneの新しいマスコットとして「しまクリ三兄弟」が登場したようです。3つの定額「メールしまくり」「パケットしまくり」「通話しまくり」を代表する存在とのこと。なかなかかわいいです。ドコモダケ、auシカに続いて、次はVodafoneノミかという噂までありましたが、さすがにノミはなかったようですね (^^;)。

最近は日本でも比較広告を見かけることが多くなってきました。ペプシとコカ・コーラ、WindowsとLinux等。「アレよりカンタン」なレコーダーというのもありました。比較広告は、消費者から見れば、複数の選択肢からひとつのものを選ぶ上で親切なようにも思えますが、必ずしも公正だとは言えません。コーラの味や、レコーダーの使いやすさ等は主観的なものですし、ある点の比較だけで商品全体の比較だできるものではありません。携帯キャリアの例でいえば、料金は定量的に比較できますが、実際の選択時には通話エリア等も非常に重要なウェイトを占めてきます。

情報が正しいからといって、必ずしも公平な情報だとは限りません。偏よった情報だけにふりまわされないよう、この種の情報は複数リソースから取得するようにして、総合的な判断をするようにしたいですね。

【関連リンク】
比較広告に関する景品表示法上の考え方(公正取引委員会事務局)
ボーダフォンのマスコットは「クリ」だった (ITmedia)
幻ではなかった――auシカ、沖縄で公式発表 (ITmedia)
Microsoftの対Linux比較広告
コーラの二元論 (コーラ白書)
東芝DVDレコーダー「AKシリーズ」の意味は「アレよりカンタン」

volatileで最適化を抑制する

プログラムは思った通りに動かない、書いた通りに動く。
(プログラミングの格言)

C言語やC++,Javaにはvolatileという修飾子があります。組み込み系ソフトウェアやマルチスレッドのアプリケーションを書いている方にとっては、なじみ深い存在ですが、そうでない方にはあまり縁がないのかもしれません。しかし、volatileの使い方や存在意義を知らないままコーディングを行うと、思わぬバグを引き起こす場合があります。今回は、そのvolatileキーワードについて簡単に説明したいと思います。

volatileは初期のCであるK&Rには含まれていませんでしたが、ANSI C(C89)以降のC標準規格にはconstと一緒に含まれるようになりました(constとvolatileをあわせてcv修飾子と呼ぶこともあります)。一般的なCなら必ず備えている修飾子です。

volatile修飾子の意味ですが、「プログラミング言語C ANSI規格準拠」によると、
volatileの目的は,黙っていると処理系で行われる最適化を抑止することにある.例えば,メモリ・マップ方式の入出力をもつマシンでは,ステータス・レジスタに対するポインタは,ポインタによる見かけ上,冗長な参照をコンパイラが除去するのを防ぐのに,volatileへのポインタと宣言することが可能である.
となっています。 なぜ最適化を抑止する必要があるのか?例をあげて説明します。

条件分削除の抑制

次のコードを見てください。変数の値が変化するまで、変数をポーリングし続けています。
extern int event_flag

void poll_event()
{
  while (event_flag == 0) {
    /* 処理。但しevent_flagは操作しない */
    ....
  }
  ....
}
whileの続行条件としてグローバル変数event_flagの値を参照していますが、ループ内部でevent_flagの値を一切処理していません。よって、単純に見ると、このwhileの条件は毎回評価する必要なく、次のような最適化が可能となります。
void poll_event()
{
  if (event_flag == 0) {
    while (1) {
      /* 処理。但しevent_flagは操作しない */
    ....
    }
  }
  ....
}
不要な条件評価を削除するのは最適化の基本であり、実際、大抵のコンパイラでは上記相当のコードが生成されます。
このような最適化は処理速度の向上にとって大変有難いのですが、上記の関数は、本当にこのような最適化をしても良いのでしょうか?
シングルスレッドモデルの場合は、特に問題はありません。問題は、この変数が他のスレッドや、ハードウェアによって書き換えられる可能性がある場合です。つまり、上記のループはその関数をコールしているスレッド自身ではなく、割り込み処理や他のスレッドからの操作されることによって変化することを期待していたという場合です。
この場合は、上記のような最適化がなされた場合、途中で他スレッド、あるいは割り込み処理が値を変更したとしても、最適化のためにループから抜けることがなくなってしまいます。コンパイラは、現在の関数コンテクスト以外からその変数を操作することはないという前提にたって最適化を行っているため、このようなことが起こります。

そこで、このような複数スレッドからアクセスされる変数に対する最適化を抑制するために、volatile修飾子を使用します。具体的には、次の例のように、変数宣言にvolatile指定を追加します。
extern volatile int event_flag
これで、event_flagに対する最適化は抑制され、先ほどのような意図しない最適化も防ぐことが出来ます。

処理手順の保存

条件分岐の最適化以外にも、処理手順の最適化によって意図しない動きになることもあります。次の例を見てください。
extern int* p_regster1;
extern int* p_regster2;

void set_regester2(int val)
{
  /*必ず次の手順でレジスタ設定する必要がある*/
  *p_register1 = 1;
  *p_register2 = 0;
  *p_register2 = val;
  *p_register1 = 0;
}
register2の値を設定する前に、regster1を一旦1にしており、register2設定後今度は0を設定しています。組み込みソフトウェアなどでは、HWの仕様上このような一見(Cコード上)無駄な手順が必要なことがあります。 これも一般的なコードとして見れば冗長なので、volatile指定無しの場合、最適化される可能性があります。例えば、次のようになります。
void set_regester2(int val)
{
  *p_register2 = val;
  *p_register1 = 0;
}
このような最適化は、単一のスレッドからしか呼ばれない関数でも起こりえます。回避策は、先ほどと同様、変数のvolatile指定をつけることです。
extern int* p_regster1;
extern int* p_regster2;

4434046683 このようにvolatile指定は、組み込み系やマルチスレッドプログラミングで重要な役割を果たしており、その動作を理解することは重要です。

近年のコンパイラの最適化能力はすばらしいので、アセンブラを強く意識したコードを書く必要はなくなってきました。しかし、最適化による影響を正しく理解していないと、上記のように予期せぬ不具合に見舞われることがあります。仮想関数、ガベッジコレクション等、言語レベルでの機能も高級化してきていますが、その根底にある動作をある程度把握していないと、最適なコードは書けません。

最適化に関しては、デバッグ時にデバッガが表示する変数の値に関する注意などもあるのですが、それはまた別の機会に。

【関連リンク】
「組み込み」ならではの基礎知識 (組み込みネット)
法大奥山研究室:C言語 volatile
特集:スレッドの落とし穴 (ITmedia)

【関連書籍】
実践マルチスレッドプログラミング Steve Kleiman
Pthreadsプログラミング ブラッド・ニコルス 他
CプログラミングFAQ―Cプログラミングのよく尋ねられる質問 Steve Summit 北野欽一
Cプログラミングの落とし穴 Andrew Koenig 中村明
C/C++による組み込みシステムプログラミング
組み込みLinux入門―開発環境/デバイスドライバ/ミドルウェア/他OSからの移行
ITRONプログラミング入門―組み込みOSのデファクト・スタンダード プログラミング詳細とサービス・コール徹底解説

本のおすすめ

4873115655

4274065979

4822236862

4274068579

4822255131

B00SIM19YS


プロフィール

  • Author:proger
  • 組み込み関係で仕事してます

ブログ内検索

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。