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編~ (闘わないプログラマ)
直接数値を書いても悪くはない場合(フィンローダのあっぱれご意見番)

コメント

非公開コメント

本のおすすめ

4873115655

4274065979

4822236862

4274068579

4822255131

B00SIM19YS


プロフィール

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

ブログ内検索