C言語が嫌いな理由

2007/1/4

Why I hate C」という記事がありました。 私は個人的にはC言語が好きですが、C言語が嫌だという視点も面白いので要約してみました。 かなり削っているので詳細は原文をご覧下さい。


C言語は組み込みに使うには良い言語ですが、その他の99.9%のアプリケーションを作るには最適とは言えません。 現在、アセンブラが一般的なアプリケーションを書くための良い解では無いことは自明です。 ここでは、もはやC言語もそうでは無い理由を述べたいと思います。

C言語の最も大きな問題はプログラマが間違いを犯しやすい事です。 私も良く間違えます。 どんなプログラマであっても数千行のコードを書いてバグが一つも無いということはありません。 コード量が少ないということは間違いの数も少ないということになります。 C言語は、言語のデザイン上、より多くのコードを書く事を要求します。 また、新しく開発されたプログラミング言語と比較して、間違いを防いだり発見したりする機構が不足しています。 良いプログラミング言語は、一般的な事をするときには出来るだけの事をするべきですし、少ないコードがベストなコードなので、可能な限り冗長性は排除すべきです。

エラー処理の不整合

C言語には、一箇所で全てのエラーをハンドルするような機構がありません。 エラー処理は、プログラムの正常系ではexceptionとして処理されるべきであり、正常系の一部として実装されるべきではありません。 標準ライブラリは、以下の方法でエラーを通知できます。

  • 0を返す
  • 0以外を返す
  • NULLを返す
  • errnoをセットする
  • 他の関数を呼ぶ事を要求する
  • セグメンテーションフォルト、もしくはその他クラッシュ

今まで書いたC言語のコードを見直してみてください。 どれだけがエラー処理に費やされているでしょうか? また、どれだけがアプリケーションが本来すべき事をやっているでしょうか? あなたが今まで書いたアプリケーションのうち、何個がエラーから正しく復帰できるでしょうか?

あなたが最後にprintfの返り値をチェックしたのは、いつでしょうか? もしくは、printfの返り値の正しいハンドリング方法を知っているでしょうか? もし、printfの返り値をチェックしないのが問題ではないとすれば、何故mallocやfwriteの返り値をチェックしなければならないのでしょうか?

構文が曖昧で一貫性が無く、直感的ではない

C言語では、正しくないコードを書くことは簡単ですが、発見するのが難しいです。 例えば、「char *foo = "bug"」という文字列は以下のようにアクセスできます。



foo[0] ==> 'b'
1[foo] ==> 'u'
*(foo + 1) ==> 'g'


例えば、論理的な比較をするときに「==」とすべきところを「=」にしてしまう間違いはあまりに一般的です。 代入演算子は、比較演算子と明確に区別されているべきです。 自分で書いたコードでは、この問題はうっとしいです。 他人が書いたコードでは、この問題は悪夢です。



if (foo = bar + 1) { ... }


書かれたコードのセマンティクスを理解するのに時間をかけないと、それを書いたプログラマが「=」として使いたかったのか、それとも「==」の間違いなのかはわかりません。

文字列ポインタへの配列と、文字列を引数として受け取りbooleanを返す関数へのポインタを引数として取る、関数のポインタを宣言してみてください。 C言語での記述方法を悩むうちに、Pascalでは以下のようにすぐに書けてしまいます。



type foo = function(strs: array[] of string; 
    function(str:string):boolean): string;


あと、Dでは以下のように書けます。



typedef char[] function(char[][] strs, 
    bool function(char[] str)) foo;


なぜ、3つのint変数を以下のように宣言できて、



int a, b, c;


関数の引数は以下のように宣言できないのでしょうか?



void foo(int a, b, c);


なぜ、そこら中のヘッダファイルに#ifndef、#define、#endifを書いてまわらないといけないのでしょうか? #importがあればかなり昔に問題を解決できたはずなのに。

Cには基本が欠けている

70年代から文字列処理は必要だったはずですが、そのような基本的なことでさえ、C言語には欠けています。 これにより、開発者に車輪の再発明を強要しています。

  • 文字列
  • 配列
  • ハッシュテーブル

さて、あなたは連結リストを何度実装した事がありますか?

プログラマが環境の子守をしなければならない

多くのCプログラマは、他の言語はプログラマの子守をしている、と主張します。 私が抱えている問題は、私がCの子守をしないといけない事です。

真っ先に思い浮かぶのは、ガベージコレクションです。 正しくやれば、手動で行った方が実行速度が速いと言われています。 ただし、正しくやるのが非常に困難です。 そのポインタが指している先が確保されているかどうかをC言語で知る事は出来ません。 そのような事はプログラマが面倒を見てあげないといけません。

エラー処理の複雑さが終了処理を一箇所にまとめる事を困難にし、結果としてメモリリークや不正なポインタをアクセスしたり、2度同じポインタをfreeしてクラッシュするアプリケーションが出来上がります。 実際には、悲劇を避けるためにプログラマは解放機構の中に無駄なチェックをどんどん追加して、メモリ解放機構はどんどん複雑になっていきます。 結局、ガベージコレクタが手動で書いた解放機構に勝ってしまいます。

型の安全性とチェック

型の安全性(Type safety)はもう一つの問題です。 例えば、printfなどでは、渡す引数のチェックはプログラマに任されています。

コードを堅牢でセキュアにするためには、型の安全性は非常に重要です。 プログラマは間違いを犯します。 間違いをチェックするためのコードの中でさえ間違いを犯します。

コンパイラの機能としてチェック機能を提供せずに、全てのプログラマにチェック機能を再開発させる事は資源の無駄です。

移植性神話と実際

Windowsを考えなければ、C言語でだいたいポータブルなコードを書くことは可能です。 しかし、この「だいたい」という表現が曲者です。 #ifdefや、その他のプリプロセッサ小技を駆使すくことは移植性を高める行為ではありません。 既知のプラットフォーム用に複数のコードを書いているに過ぎません。 それでは、新しい未知のプラットフォームには対応できません。 もし、C言語がポータブルであると言うのであれば、あなたのマシンでCコンパイラがwarning無しでコンパイルしたコードは、他の環境でも同様にコンパイルできなくてはなりません。

で、結局何を使えばいいの?

これだけ書いたので、何を使うべきかを書いておくべきかも知れません。 しかし、私はあえてどの言語を使うべきかは述べません。 世の中にどのような言語があって、どれが一番自分にあっているのかを探すのはあなた自身の仕事です。 「どのプログラミング言語が最も優れているか」という質問に回答はありません。 良い言語はすでにたくさんあります。 また、今後も出てくるでしょう。

自転車に乗るのに練習が必要だからといって、歩くのが最も良い移動の方法ではない事はわかります。 あなたは過去に歩く事を練習しました。 同様に、C言語以外の言語もやればすぐに習得できると思います。

最近のエントリ

過去記事

過去記事一覧

IPv6基礎検定

YouTubeチャンネルやってます!