Jun 21, 2008

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()は、どうせまとめて焼却されるゴミは分別するコストが無駄、というアナロジーででも割り切って省くべきではないだろうか。


Posted at 23:01 in PC一般 | WriteBacks (3)
WriteBacks

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

Posted by eternalharvest at 02/02/2010 12:55:29 AM

もちろんexit()時に解放されるリソースはOSや使用するライブラリによって異なります。 私も組み込み系の仕事をしているので、コードはOSによって異なるのが当たり前だと思っています。上の記事もその認識で書いています。 ファイルディスクリプタが指すリソースがプロセス終了時に解放されないことがあるのは普通だと思います。(手元のBSD系OSでもそうでした)

Posted by ynomura at 02/02/2010 08:33:11 PM

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

Posted by at 02/24/2010 04:06:09 PM

writeback message: