2009年08月07日
K&R C ができるまで
少し前の話になりますが、いただいたコメントをきっかけに、puts になぜ改行が付くのかというネタについて調べていた時、面白い論文を教えてもらいましたので紹介します。
"C and the AT&T Unix Port -- A Personal History", Stephen C. Johnson (翻訳)
http://roguelife.org/~fujita/COOKIES/HISTORY/USENIX/johnson.html
"C and the AT&T Unix Port -- A Personal History", Stephen C. Johnson (翻訳)
http://roguelife.org/~fujita/COOKIES/HISTORY/USENIX/johnson.html
初期の UNIX v6 を書いた pre K&R C が、32 bit マシンへの移植を意識した時、どのように K&R C へと変遷していったかという歴史が書かれています。C に関しての疑問を調べていくと、やはり初期の言語 B や PDP-11 とのかかわりが鍵となってきます。
The Impact on C という章には、以前書いた pre K&R C についての記事でも触れた、キャストが無い状況で符号無し整数やメモリアドレスを直接扱うためのトリックなどについても触れられています。
PDP-11 は 8 進数で理解するのが自然なアーキテクチャだったようです。10 進数や 16 進数がデフォルトの今日の私たちは、UNIX v6 のソースコードを見たときに感覚が合わなくて苦労するのですが、Dennis M. Ritchie や論文の著者らも同様に、8 進数から 16 進数の big-endian マシンに移行する際には苦労したみたいですね。
前々回の記事で、pre K&R の goto が任意の int * 型の式を取れるという仕様(そしてなぜ括弧がつかないのかという疑問)について少し触れました。
そして、これは FORTRAN の computed goto という言語仕様を参考にしたため、括弧が付かないのではないか ? という考察をいただきました。私は FORTRAN は全く知らないのですが、現在の GNU C (GCC 独自の C 拡張) にも同様の機能 (&& でラベルのアドレスを void * として取得できる lavels as values という機能もあり、任意の void * に対して computed goto できる) が存在することは知っていました。
K&R C 以降から computed goto が無くなってしまったのは、個人的にはけっこう残念だと思います。一般的には今の goto でさえ、スパゲッティコードの原因とされて必要以上に嫌われている感じがしますが、インタプリタの高速化 (direct threaded code と呼ばれる手法) や、C コードを生成するプログラム合成システム(私は大学院でそのような研究をしていました)を開発している時などには欲しい機能です。
標準化が進むにつれて、C はより健全でポータブルな言語になってきたわけで、これは基本的に正しい進化だと私も思うのですが、やはり初期の自由奔放な能力は失われてしまった部分もあると、少し寂しく感じます。
現在の goto が飛べるのは、同じ関数内のラベルのみに制限されているので、実は言われているほど危険ではありません。(もちろん関数1つが 5000 行とかあれば話は別ですが、それはまた別の問題です。)
確かに昔の FORTRAN や C の goto は本当にどこにでも飛べたようですし、関数の途中にいきなり飛び込む entry という機能 (pre K&R では keyword のみが存在し、実装はされなかったようです) なども多用されましたから、ダイクストラなどが 「構造化プログラミング」を提唱したわけです。
そんなこんなで前後関係は不明ですが、結果的に computed goto は歴史の流れの中で廃止され、必要不可欠な場合の手段として setjmp/longjmp が標準関数化されることとなりました。
その後の C++ には継承や例外という機能が追加されましたが、これらも便利な反面「スパゲッティ継承」によりプログラムの見通しが悪くなる危険性や、「例外は現代の、より性質が悪く制御不能な goto」などと批判される場合もあります。歴史は繰り返すというか、やはり便利な機能自体が悪いのではなく、何事もいきすぎた機能の乱用は良くないということなのでしょうか。
The Impact on C という章には、以前書いた pre K&R C についての記事でも触れた、キャストが無い状況で符号無し整数やメモリアドレスを直接扱うためのトリックなどについても触れられています。
PDP-11 は 8 進数で理解するのが自然なアーキテクチャだったようです。10 進数や 16 進数がデフォルトの今日の私たちは、UNIX v6 のソースコードを見たときに感覚が合わなくて苦労するのですが、Dennis M. Ritchie や論文の著者らも同様に、8 進数から 16 進数の big-endian マシンに移行する際には苦労したみたいですね。
前々回の記事で、pre K&R の goto が任意の int * 型の式を取れるという仕様(そしてなぜ括弧がつかないのかという疑問)について少し触れました。
そして、これは FORTRAN の computed goto という言語仕様を参考にしたため、括弧が付かないのではないか ? という考察をいただきました。私は FORTRAN は全く知らないのですが、現在の GNU C (GCC 独自の C 拡張) にも同様の機能 (&& でラベルのアドレスを void * として取得できる lavels as values という機能もあり、任意の void * に対して computed goto できる) が存在することは知っていました。
K&R C 以降から computed goto が無くなってしまったのは、個人的にはけっこう残念だと思います。一般的には今の goto でさえ、スパゲッティコードの原因とされて必要以上に嫌われている感じがしますが、インタプリタの高速化 (direct threaded code と呼ばれる手法) や、C コードを生成するプログラム合成システム(私は大学院でそのような研究をしていました)を開発している時などには欲しい機能です。
標準化が進むにつれて、C はより健全でポータブルな言語になってきたわけで、これは基本的に正しい進化だと私も思うのですが、やはり初期の自由奔放な能力は失われてしまった部分もあると、少し寂しく感じます。
現在の goto が飛べるのは、同じ関数内のラベルのみに制限されているので、実は言われているほど危険ではありません。(もちろん関数1つが 5000 行とかあれば話は別ですが、それはまた別の問題です。)
確かに昔の FORTRAN や C の goto は本当にどこにでも飛べたようですし、関数の途中にいきなり飛び込む entry という機能 (pre K&R では keyword のみが存在し、実装はされなかったようです) なども多用されましたから、ダイクストラなどが 「構造化プログラミング」を提唱したわけです。
そんなこんなで前後関係は不明ですが、結果的に computed goto は歴史の流れの中で廃止され、必要不可欠な場合の手段として setjmp/longjmp が標準関数化されることとなりました。
その後の C++ には継承や例外という機能が追加されましたが、これらも便利な反面「スパゲッティ継承」によりプログラムの見通しが悪くなる危険性や、「例外は現代の、より性質が悪く制御不能な goto」などと批判される場合もあります。歴史は繰り返すというか、やはり便利な機能自体が悪いのではなく、何事もいきすぎた機能の乱用は良くないということなのでしょうか。