« 弾性衝突円盤アプレットの設計メモ | メイン | ビット数を数えるルーチン »

 C言語の悪習

ある所で、C言語のソースに

if (NULL == pointer){...
という表記があるのを見て、好ましくない書き方だ、と言ったら、一緒にいた2人に、この書き方には理由がある、この書き方もありだ、というようなことを言われた。
このC言語特有の"=="の左側に定数を置く書き方は、知ってると通っぽい、知ってることを自慢したくなる、不思議な匂いを漂わせるようだ。プロはみんなこう書いている、という錯覚を起こさせることもあるようだが、事実は全く逆で、まともな書籍には書かれない、最もわかってる人々は使わない書き方だ。著名なオープンソースではマイナーである。
しかし、これが好ましくない書き方であることを簡潔に説明することは難しく、また100%誤りと言えるものではなく、一応利点もあることから、根強い人気があり、まるで宗教論争のように平行線の議論が繰り返される。

C言語のスタイルでよく議論になってるのを見かけるトピックは他にもある。それらと合わせて、個人的な見解を書いてみる。

(1) 定数==変数
"NULL == pointer"に限らず、

0 == integer
とか
true == boolean
というのも見かける。
肯定派の根拠はもちろん、変数==定数という書き方だと、if文などの条件式で間違って(変数=定数)と書いた場合にコンパイルエラーにならないため、バグの元になりやすいというものだ。
しかし、それが唯一の利点であることが、逆に好ましくない理由を示している。もしその利点が無ければ、標記のような書き方は誰もしないだろう。その理由は、読みにくいからである。
その利点には、可読性を犠牲にするまでの価値があるだろうか。

(計算式=定数)だと定数を左にしなくてもコンパイルエラーになるし、(変数=変数)だとコンパイルエラーにする類似の手段はない。まさかそういう間違いをコンパイルエラーにするために普段から(0+変数==変数)と書く人は居るまい。救われるのは"=="と"="の書き間違い全てではなく、(変数=定数)のみである。
それも、現在広く使われているコンパイラでは、条件式の部分が(変数=定数)になっているとワーニングを出してくれるので、さほど問題ではない。それに、そもそもそんな書き間違いは稀であろう。

変数と定数の比較の時だけ比較対象を左に書き、それ以外の比較では比較対象を右に書く、というのは、コーディングスタイルとして整合性に欠けるのではないか。それとも、定数==変数と書く人は、必ず比較対象を左に書くのだろうか?
C言語以外の、比較対象の定数を比較演算子の右に書ける言語では、定数を左に書く人は居ないだろう。C言語の限られた比較演算の場合だけ、役立つかどうか不明な些細な利点のために、敢えてわかりにくい書き方をするべきだろうか。

(2) (char)NULL

char c = (char)NULL;
というやつである。もちろん正しくは
char c = '\0';
だ。
これの支持者は'\0'より(char)NULLの方が「ヌルキャラクタ」として理解しやすいし、(メジャーな誤用のため)よく見かけるし、(char)NULLが'\0'と異なる値になる処理系はまず存在せず誤動作しないので、間違いだと気付かないのだろう。
しかし、ANSIの言語仕様でNULLは「ヌルポインタ」であり、いかなるオブジェクトも指さないポインタの値と決まっている。C言語的には(int)NULLは0でなくても良いのだから、(char)NULLを'\0'の意味で書くのは誤りである。
それでも、実際に問題が起こらないなら読みやすい方がいいだろう、と言われれば、返す言葉が見つからない。

(3) プログラム終了時でもmalloc()したメモリは必ずfree()すべし
コーディングスタイルやコーディングルールとして、malloc()には必ず対になるfree()を書く、というのがある。習慣的に静的にメモリリークを防ぐためには、基本的には好ましいことであるが、main()を抜ける直前やexit()の直前のfree()は無駄である。争点は、そのようなfree()も書くべきかどうかだ。
free()に限らず、ソケットのclose()やファイルポインタのfclose()など、exit時にOSに返されると規定されているリソース全てについて同じ話が当てはまる。

終了時もfree()すべし派の言い分は、
・その方がプログラムの構造として美しい
・free()して害になることはほとんど無い
・exit()後に処理があると、free()を怠ることにより問題が生じる可能性がある
というのがあったのを記憶している。ソースコードの静的解析ツールを使うと警告が出てしまう、というのもあったかも知れない。3つ目はatexit()やon_exit()のことを言っているが、これは世界にそんなコードが1つあるか無いかというオーダーのレアケースであろう。

そりゃ本当に無害なら書いた方が良い、終了直前のfree()は省略できるというのは知らなくてもいい無駄な知識、だとは思うが、終了直前のfree()も書くとコンパイルされてマシン語コードになってしまうのである。例えばfree()1つくらいなら10~20バイトくらいかも知れないが、それも10個になると100~200バイトにもなる(PowerPCで試した1つの例だと120バイトになった)。それだけ実行ファイルのサイズが無駄に大きくなってしまうのである。

例えばソースコードの読みやすさやメンテナンス性を犠牲にしてまで実行ファイルのサイズが小さくなる書き方を選ぶべきかと問われれば、よほどサイズがクリティカルなプロジェクトで無い限りNoであろうが、終了直前のfree()を省略して害になることはほとんど無かろう。ゴミを作り出すコードを埋め込んでまで、ある種のプログラムの対称性を重視する価値があるだろうか。

自分が書くコード、目の前にあるコードに整っていることを求める気持ちはよくわかる。例えばもし、C言語のソースファイルの最後の閉じ括弧は書かなくても良くて、書かない方がオブジェクトコードが小さくなる、というコンパイラがあったとしたら、個人的には最後の閉じ括弧を書かないのは気持ち悪すぎてできない。書いてコメントアウトするのも同様である。読む用とコンパイル用に閉じ括弧ありのソースとなしのソースを別に作るとしても、コンパイル用のソースの閉じ括弧を消して保存する操作が違和感の極みであり、コードを見ながらの手作業ではかなり後味が悪いだろう。

それでも、私はやはり実利を取るべきであり、ゴミを生むコードは書くべきでないと思う。終了直前のfree()は、どうせまとめて焼却されるゴミは分別するコストが無駄、というアナロジーででも割り切って省くべきではないだろうか。

トラックバック

このエントリーのトラックバックURL:
http://ynomura.dip.jp/cgi-bin/mt/mt-tb.cgi/86

コメント (7)

eternalharvest:

僕自身も、終了直前の free() は呼ばなくてもいいとは思うが、
ゴミを生むコードは書くべきではないというのは言い過ぎだと思う。
以前、組み込み系の OS でファイルディスクリプタを開放せずに、
終了するプログラムを書いたが OS がリソースを開放してくれなかった。
組み込みシステムで使われるような OS は例外的かもしれないが、
OS がプロセスの終了時にリソースの開放を確実にしてくれるのかは疑問。
POSIX あたりで、規定されているのだろうか?

ynomura:

もちろんexit()時に解放されるリソースはOSや使用するライブラリによって異なります。
私も組み込み系の仕事をしているので、コードはOSによって異なるのが当たり前だと思っています。上の記事もその認識で書いています。

ファイルディスクリプタが指すリソースがプロセス終了時に解放されないことがあるのは普通だと思います。(手元のBSD系OSでもそうでした)

匿名:

http://jbbs.livedoor.jp/bbs/read.cgi/computer/5651/1048816258/150
> if(変数=定数)なんてやっちゃうカスは、ど~せ他んところでバグだらけだろうから所詮カス。
> if(定数==変数)は、私はカスなので↑やっちゃうかも、って宣言してるみたいで、所詮カス。
> そんなコード見たら不安で仕方ない。

匿名:

CやC言語から入った人間なら、定数が左辺にあっても慣れているので読みにくいとは感じない。
慣れているから。慣れている人には、定数が左にあったほうがコーディングでのミスに気付きやすいので
利点がある。なので、それを利用するのは自然。

よく議論として上がるのは、そんな問題はテストで分かるという話だが、そんなのはあたりまえ。
それよりも、日々のコーディングのちょっとしたコンパイル時に気づきたいのが使う理由。

左か右かの可読性の話は、英語と日本語のどちらが優れているかという論議と似ていて、どちらかを
非難するのは意味がない。ネイティブな言語が使いやすいに決まっているから。

まあ、この意見は掲載されないのだろうな。
本人が慣れていないっていう理由だけでアウトとする差別的な記事を書く人は。

ynomura:

コメントありがとうございます。
ただ、私が慣れてないっていう理由でアウトだ、なんてことは言ってないですよ。
書いた本人が慣れているかどうかは関係なく、他の人にとって読みにくいということと、比較対象が定数の時だけ比較対象を左に書くのは不自然だということです。

それに、私は仕事柄、様々な会社の何十人ものプログラマーと接してきましたが、その中にはC言語から入った人も多かったようですが、慣れてるから定数のみを==の左辺に書くと言う人を聞いたことがありません。たまに定数を左に書く人を見る度に聞いていますが、大体、比較対象が定数だとわかったら、そうするものだと意識してわざわざ左辺に書かれています。

匿名:

終了直前の free() に関しては、私は入れたほうがいいと思います。

理由は、そのコードに暗黙の前提条件が入ってしまうために将来の再利用が制限される可能性があるからです。

たとえば、改造の結果、そのモジュールが終了直前にならなくなった、とか、そのモジュールが切り出されてデーモンに組み込まれたとか。

もちろん、free() を書かずにその旨コメントでソースコードに書いておくというのもアリですが、そんなことするくらいなら初めから書いておいたほうが自然でいいかなと。

ynomura:

コメントありがとうございます。
確かに、終了直前の free()を省くのは、再利用を前提としない話であり、そのことを明確に書くべきでした。

この話は、昔からある議論の延長で書いたもので、終了直前のfree()は不要な環境(例えばANSI Cの処理系)であっても「必ずfree()するべきである」にこだわるのが問題だというのが主旨なのです。
その議論においても、malloc()したメモリーは明示的に全てfree()するように書くのが良い習慣とされていることは否定されていませんが、この記事では敢えてそれを結論にしませんでした。

コメント投稿フォーム

※投稿されたコメントはオーナーが承認するまで表示されません。


Powered by Movable Type 3.35