Help us understand the problem. What is going on with this article?

C89 による bool の扱い

More than 1 year has passed since last update.

これは C 言語 Advent Calendar 2018 の記事です。

LuneScript の Advent Calendar 作成の合間に、ちょっくら C 言語 Advent Calendar にお邪魔します。

C 言語

単に「C 言語」といっても、それが何を指すのか一意に定まりません。なぜなら「C 言語」には C89, C90, C99, C11 の規格があるからです。

ただ、日本国内において「C 言語」といった場合は、C89 を指すことが多いように思います。というか、 C90, C99, C11 の存在を知らないエンジニアが多いのではないでしょうか?

まぁ私自身、普段全く縁の無い複素数の _Complex 型が C99 で追加になっていると言われても、「へぇ〜」としか思いませんし、規格を意識していないこと自体は珍しくないと思います。

で、この記事の本題は C89 に関するネタです。

2018 年に C89 のネタを書くのもどうかと思いますが、温故知新ということで時間のある方はお付き合いください。

C89 による、とあるコードレビューシーン

次のように引数に与えられた整数が奇数かどうかを判定する関数を作成したとします。

bool isOdd( int val ) {
    if ( ( val % 2 ) == 0 ) {
        return FALSE;
    }
    return TRUE;
}

この isOdd() 関数を使って、 1 〜 10 の値の内、奇数だけを表示するプログラムを作成すると、次のように書けます。

int count = 0;
for ( count = 1; count <= 10; count++ ) {
    if ( isOdd( count ) ) {
        printf( "%d ", count );
    }
}

以前(といってもつい最近ですが)、上記のようなコードをレビューしたとき、次のような指摘がありました。

「if ( isOdd( count ) ) ではなく、if ( isOdd( count ) == TRUE ) にすべき」

たぶん多くの方は、「何言ってんの?」と思うのではないでしょうか?

イマドキの言語では、この指摘とは全くの逆で、「if ( isOdd( count ) == TRUE ) ではなく、if ( isOdd( count ) ) にすべき」のはずです。

しかし C89 においては、その指摘が全くの間違いとは言い切れないというのが実情です。

何がどうしてこう言う指摘がなされてしまうのか、を次で説明します。

C89 での論理値型の扱い

皆さんご存知の通り、 C89 には論理値型はありません。 (知らない?)

論理値型の _Bool は、C99 で追加されました。

では、「C89 ではどのように論理値を扱うのか?」というと、各プロジェクト毎に次のような定義をするのが一般的だと思います。

typedef int bool;
#define FALSE 0
#define TRUE 1

ここで typedef int bool; ではなく、 typedef char bool; とか、typedef enum { FALSE, TRUE } bool; とか、いくつかバリエーションはあるかと思いますが、今回のネタの趣旨とは外れるので、そこはつっこまないでください。

そして、ここから次の論理が導き出されます。

  • isOdd() の戻り値型の bool は、 C 言語で規定された型ではなく、ユーザが定義した型である。
  • TRUE/FALSE についても同様である。
  • よって、どの場面に置いても「TRUE が 0 以外で、 FALSE が 0 となる」保証はない。
  • つまり、isOdd() の結果をそのまま条件として使用するのは危険である。

というのが、指摘の根拠です。

まぁ、 bool と TRUE/FALSE がユーザ定義であることは確かにその通りです。しかし、最後の「TRUE が 0 以外で、 FALSE が 0 となる保証がない。」は、根拠としてあまりにナンセンスです。

「TRUE が 0 以外で、 FALSE が 0 となる保証がない。」がまかり通るなら、「特に意味なく、なんとなく TRUE/FALSE ってキーワードを定義しちゃった。てへへ」と、言っているのと同レベルです。TRUE が真で、FALSE が偽になるように定義しとるんちゃうんか〜〜い!!、と心の中でつっこみを入れました。

確かに TRUE/FALSE はユーザ定義なので、C89規格で「TRUE が 0 以外で、 FALSE が 0 」と保証されていません。しかし、規格としての保証がないにしても、ユーザ定義自体はプロジェクトでコントロールできるものなので、プロジェクトで「TRUE が 0 以外で、 FALSE が 0」を保証すれば良いだけの話です。

もしもそれが保証できないなら、そもそも TRUE/FALSE を定義をするべきではありません。

そんな保証すら出来ないものに TRUE/FALSE なんていうあまりにも一般的なキーワードを使うなんてあってはならないです。

つまり、「if ( isOdd( count ) ) ではなく、if ( isOdd( count ) == TRUE ) にすべき」という指摘はナンセンスです。もしもこのような指摘をされた場合は、論理的に反論しましょう。

なお、もしも isOdd が bool の TRUE/FALSE ではなく、int の MODE1/MODE2 を返す関数で、たまたま MODE1/MODE2 が 0/1 であるならば、if ( isOdd( count ) ) とするのではなく、if ( isOdd( count ) == MODE2 ) で判定するのは当然で、レビューの指摘はごもっともです。ついでにいうと、bool ではなく int を返す関数であれば、関数名が is で始まるのも NG だと思いますが。。。

== TRUE 問題

「== TRUE」の書式に関しては、いろいろな意見があると思いますが、少なくとも私は 「== TRUE」撲滅推進派です。

ネットを検索すると、「 isOdd( count ), ! isOdd( count ) と書くより == TRUE, == FALSE と書いた方が可読性が良い」という意見もあったりします。

しかし、それだと C# や swift などの nullable で利用する int! や int? を全否定するの?と、ツッコミを入れたくなりますし、そもそもほとんどの演算子は 1 文字なんですが。。。

もしも演算子 1 文字だと可読性に問題があるというのであれば、-10 と 10 は見分けが付き難く可読性に問題があるということになります。

そういう方は、負の値を使用する際に、次のように定義するんでしょうか?

#define MINUS (-1)
int val1 = MINUS * 10;  // -10
int val2 = 10;          //  10

12/8 追記LuneScript では、 bool の比較演算は推奨しないオペレーションとして、将来廃止予定です。

https://qiita.com/dwarfJP/items/3b2c23df16b14cd70df5

まとめ

結局この記事でなにが言いたかったかというと、C89 は論理値型一つとっても、こんなくだらないことが起り得ます。

2018 現在、C89 を使う機会はかなり減っているとは思いますが、使用する際は十分注意しましょう。

12/3 追記

ちょっと調べて判ったんですが、あの Renesas の純正コンパイラが、知らぬ間に C99 対応してたんですね。

ただ、次のような資料が 2017/4/20 に作成されているってことは、イマイチ移行が進んでいないってことでしょうかね?

https://www.renesas.com/jp/ja/doc/products/tool/doc/008/r20ut2608jj0101_rl_cd_mig.pdf

有償の C コンパイラで C99 対応していないものって、もしかしたらもう無いんでしょうか?

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away