C言語は、C++やC#など「C」を名乗る言語はもとより、1990年以降に盛んに使われるようになった各種言語の多くの源流とされている。 では、このC言語自身の起源はというと、一般には下記の系譜であると理解されている。
ところが、「C言語の構造体をめぐって」を まとめるに際して調べてみたところ、この系譜は事実の記述としてあまりにも一面的であり、系譜として「不適切」であると断言しても良いほどであるということが判った。 このことは、「C言語の構造体をめぐって」の2005年5月2日以降この文章の初稿公開日までの版にも簡単に記載していたが、これを独立させて詳論してみることにした。
まず結論を簡単にまとめておくと、以下のようになる。
CPLは「Combined Programming Language」の略である。 Oxfordが出している“the Computer Journal”という学術雑誌で概要が紹介されているのだが、あまり細かいことは判らない。 (本ページの初稿段階では、この論文の内容が判らずに苦労していたのだが、いつのまにか全文が公開されていて英語版Wikipediaからリンクされていることに2011年9月20日に気付いた。 URLのドメインからすると正規の公開サイトのように思える。)
CPLは、適用分野ごとにFORTRAN, algol, COBOLが各々利用されていたのを統合しようという、1年後のPL/Iと同じ野心を有していたらしい。 ただ、PL/Iと違ってアプローチが理論的だったようで、どうやら理想的な仕様を作ってそのまま発表してしまい、実装は直ちにはされなかったようである。 概要紹介論文によると、実際に記述するためには「3本線の等号」「矢印」「セクション記号」などの特殊な文字が必要であり、ASCIIコードさえ標準化されていない時代に、そもそもプログラムを計算機可読テキストで記述できたかどうかさえ疑問である。 Wikipedia(英語版・日本語版とも)によるとコンパイラは1970年ごろに作られたらしいということだが、記号類に関する仕様はかなり変えたのではないかと想像できる。 1970年には既にBCPLが存在しているので、BCPLに合わせた可能性も考えられる。
CPLからBCPLへ最も素直に引き継がれたのは制御構造だと思われる。 即ち、条件によって動作を変えたり、一定の条件の元で動作を繰り返したりすることを表現する形式である。 CPLでは、「そこまで細かく区別して何の意味があるんだ」と言いたくなるようなパターン分けがされていて、各々の表現形式が定義されている。 そして、それは概ねそのままBCPLへ引き継がれている。
BCPLは「Basic CPL」の略とされているが、開発当初は「Bootstrap CPL」の略だったらしい。 前者なら「基本的なCPL」であるが、後者の場合は「CPLを起動する前の準備のためのCPL」ということになる。 後者の場合、CPLの仕様が要求する全てを実装するのではなく、容易なところだけを実装して次の段階につなげるという意味になるので、結局いずれにしても、CPLを簡略化したものという意味になる。 その「簡略化」も半端なものではなく、CPLの言語仕様では巨大なシステムが要求されるのとは逆の極端へ行ってしまって、圧倒的に小さな言語仕様になっている。
簡略化の具体的な内容については、開発者自身が著書(文末参考文献参照)において、最も本質的なのは「データ型」という概念を無くしてしまったことだと述べている。 「データ型が無い」といっても、そういう概念が使えないということではなく、単に「言語仕様として用意されていない」という意味である。 つまり、データ型に相当する概念はプログラマがプログラミングで表現するという考え方である。 データ型が言語仕様として存在する場合には、同じ演算でも型によって計算機が実際に行う処理が変わるので、その分だけ言語処理系の負担が重くなる。 データ型が無ければ、同じ演算は常に同じ処理にすれば良くなり、言語処理系としての実装が軽快になるわけである。
BCPLの「データ型が無い」という特徴は、「アセンブラ的発想」でデータを扱うのに好都合である。 ここで「アセンブラ的発想」とは、データが計算機ハードウェアの中で具体的にどのような形で表現され、保持され、処理されるのかということを直接的に意識して扱うということを意味している。 BCPLは、データにどのような意味を持たせてどのように扱うかということを全てプログラミングに任せているので、自然と上述したような意味の「アセンブラ的発想」になる。
FORTRAN以来の高級言語では、データを「抽象化」し、ハードウェアの都合に密着した表現形式や処理方法などを極力意識しないように設計されてきた。 それは、これらの言語の目的が、計算機の外に存在する計算対象を、計算機内に構築したモデルで表現することだったからであろう。 つまり、計算機自身の都合というのは「邪魔物」であり「隠蔽するべきもの」だったのである。
しかし、B言語は「計算機自身の都合」に依存する分野を指向していた。 当初は特定の計算機ハードウェアで動作させるアプリケーションプログラムを記述するために作られ、次第に計算機自体の制御動作に関わる部分がB言語で記述されるようになっていったという。 このような部分は、抽象化を指向したそれまでの高級言語では記述困難であり、アセンブラ言語で記述するのが当然と考えられていた。 B言語は、このような分野を指向するために、BCPLを範としたのだと考えられる。
しかしながら、BCPLとB言語を比較してみると、「データ型が無い」という特徴以外に共通点はほとんど無いことが判る。 例えば、B言語の演算子でBCPLの影響と考えられるものは、ビット列に対する論理演算やシフト演算だけである。 それ以外の演算は、全く違っているか、さもなくばFORTRAN以来どの言語でも共通な表現か、どちらかである。
文の区切りを決定づける形式や、制御構造の表現方法、注釈の記述方法などといった構文的特徴にも共通する部分は少ない。 もちろん、制御構造の基本的な考え方などは同じであるが、これは全てのalgol系言語に共通の特徴であり、BCPLから受け継いだと評価できるものではない。 ただ一つ、algolでブロックの区切りを示す「begin end」をBCPLで「$( $)」という形に記号化したことが、B言語における「{ }」の元になったという可能性は考えられる。 Wikipedia(英語版・日本語版とも)では、そもそもBCPLにおいて、本来は「{ }」であるべきものを文字コードの都合で「$( $)」で代用していたとしている。 BCPLは「方言」の多い言語であったようなので、実際に「{ }」とも書けるものがあった可能性は考えられる。
それ以外の可能性として、BCPLが「行末を文の区切りと看做す=行末の“;”は省略可能」という構文規則になっていることが、B言語における“;”の使い方(後述)のヒントになったということも考えられなくはない。 しかし、この仮説には無理があるように思える。 B言語の開発者がよく知っていたと思われるPL/I(後述参照)と、そっくり同じ形である以上は、そちらの影響であると考えるのが自然であろう。
いずれにしても、「論理演算子」と「ブロックの区切り」は、共にBCPLがCPLから素直に引き継がずに変えた部分だということは念頭に置くべきであろう。 CPLの論理演算は概要紹介論文を見る限りでは「〜∧∨≡」というような「普通の数学記号」になっていて、初期の計算機可読テキストでは表現不能である。 BCPLはそれを表現可能な記号に変更し、それがB言語に引き継がれたらしい。 また、CPLは「ブロックの区切り」に「§」という方向性の無い記号を使っており、ブロックが入れ子になる場合は「§1」「§1.1」「§1.2」「§2」などとして区別するという不細工な仕様になっている。 これを「方向性のある記号」に変えたのはBCPLであるらしい。
結局、B言語に受け継がれたものは、CPLがBCPLに発展するに際して「新たに付加された特徴」のみであり、CPLからは何も継承していないのである。 全くの軽口になるが、「CPL」が「BCPL」になるに際して付加された「B」の部分のみが受け継がれたのだとすれば、その言語を「B」と命名したのは、極めて妥当だったということになる。 即ち、以下の等式が成立するということである。
「C言語」という命名は、アルファベット順でBの次という意味ではなく、「BCPL」の2文字目だという説がある。 開発者に近い立場の人がそう言及したという話もあるが、それは冗談だったのではないかという話もあり、確実なところは判らない。 しかし、上述したような「B言語とCPLとの間の断絶」が存在する以上、「CPLのC」が起源だとする考え方は、個人的には納得できない。
ブロックの区切りに括弧記号「{ }」を用いるのは、B言語やC言語の顕著な特徴である「視覚的表現」の代表的な事例である。 前章で述べたように、これはBCPLの「方言」に起源があるらしいし、「方向性のある記号」を用いるという意味ではBCPLの「$( $)」に発想の源泉がある可能性は高い。 さらに遡れば、CPLにおいて、方向性が無いとはいえ、キーワードではなく記号「§」を用いたことが、ある程度のヒントになった可能性も考えられる。
しかし、B言語はブロックの区切り以外でも、あらゆる構文要素に関して徹底的に記号文字による視覚的表現を使い、英文字列を避けている傾向が顕著である。 例えば、B言語では全ての演算子が記号化されており、英文字列の演算子は存在しない。 これは、B言語→C言語の流れを組む以外の言語には見当たらない特徴である。 さすがに基本的な四則演算は多くの言語で記号化されているが、整除算や論理演算に関する演算子は英文字列であるものが多い(BCPLでも等価演算は英文字列)。 このように考えてみると、「視覚的表現」が元々はBCPLを参考に始まったものだったとしても、B言語がそこから独自に大きく発展したとは言えるであろう。
さて、話を「ブロックの区切り」に戻すと、元々のalgolにおけるブロックの区切りは、視覚的表現ではなく「begin end」というキーワードである。 実は、本来のalgolの理論的定義では、「begin」というのはそういう「1つの文字」であって、「b」「e」「g」「i」「n」の5文字からなる識別子(identifer:変数や関数を識別する“名前”)とは別のものということになっている。 しかし、そのように実装するわけにはいかないので、その後の実用的な言語、例えばPascal系言語においては、「begin」という識別子の存在を禁止している。
しかし、いずれしても「begin」というキーワードをブロックの開始を示す「括弧記号」であると見るのは、人間の直観に反する。 「{ }」という素直な括弧記号を用いるようにしたことは、BCPL→B言語の素晴らしい成果の1つである。
Microsoft Windowsが実用的に使えるようになる1990年代半ば以前、多くのパソコンユーザがMS-DOS上のプログラミングテクニックを競っていた時代に、「C言語 対 Pascal」という優劣論争があった。 そのとき、Pascal派はC言語流の「{ }」を徹底的に嫌って、そんな構文ではプログラムが読みにくいとまで主張していたのだが、筆者にはこれが全く理解できなかった。 実は、筆者はC言語より先にPascalを習得しているのだが、「begin」「end」がどうしても「括弧記号」ではなく「変数」に見えてしまい、分離記号“;”の問題(後述)と合わせて、辟易していたのである。 そんなときにC言語の「{ }」構文に出会い、こんな素晴らしい解決方法があったのかと感動した。 この感覚に全く逆行する主張は到底理解できない。
もちろん、記号を多用するということは、表示環境を限定することにつながる。 実際、FORTRANなどの古い言語では、記号類の少ない表示環境を前提にしており、使う記号類を強く限定している。 C言語のANSI標準を制定する際に、記号類の少ない表示環境への対応が問題になったのだが、結局「trigraph」と呼ばれる酷い仕様が採用されてしまった。 括弧記号が対称性を失って括弧記号に見えなくなってしまう構文なのである。 意見の対立(おそらく、既に使われている流儀が多々あり、どれか一つに決められなかったのではないかと思われる)がどうにも調整できず、ヤケクソの妥協で採用されたと伝えられているが、それにしても、もう少しマシなものにならなかったのだろうか……
B言語の構文的特徴を先行する諸言語と比較してみると、PL/Iと共通する特徴が少なくないことに気付く。 具体的には、注釈を「/* */」で記述することや、“;”を分離記号ではなく終端記号として用いることが典型的である。
よく知られているように、B言語やC言語はunixの記述言語として開発されたものであり、そのunixの開発者は、Multicsの開発に関わっていた。 unix(当初はunicsと綴った)という命名も、Multicsの失敗を念頭に置いて、「multi(たくさん)」ではなく「uni(1つ)」を目指すんだという主張だと言われている。 そして、このMulticsは記述言語にPL/Iを採用していたらしい。 即ち、B言語やC言語の開発者はPL/Iをよく知っていたのである。 B言語がPL/Iの流れを組んでいるとする直接証拠は、今のところ筆者の手元には無いが、状況証拠としては充分ではないだろうか?
ネット上の情報を検索してみたところ、“;”を分離記号ではなく終端記号として用いるのはJOVIAL(Jules Own Version of the International Algorithmic Language)が発祥であり、これがB言語やC言語に影響を与えたという説が見つかった。 algol 58という初期バージョンのalgolを元に組込システム用に開発された言語で、米空軍が戦闘機の制御に利用していたらしい。 JOVIAL(J73)というバージョンを解説した1981年の文献(米空軍の技術文書?)というのがネットに転がっていたので見てみると、確かにalgolの「begin〜end」の形式を保って、ただ「end」の直前にも“;”を入れた内容になっている。 開発が1959年ということは、PL/Iより早いことも確かだろう。 しかし、B言語の開発者がこのJOVIALを知っていたかどうかについては何の情報も得られておらず、B言語の元になったという説の信憑性は判断できない。 JOVIALがPL/Iに影響を与え、間接的にB言語にも影響したに過ぎないという可能性も考えられる。
“;”を文の分離記号(separator=並んでいる最後の文の後には置かれない)ではなく終端記号(terminator=途中だろうと最後だろうと全ての文の後に必ず置く)として用いるというB言語の流儀は、おそらく直接にはPL/I起源であろうと前章で推論した。
冒頭のCPLの説明のところでも触れたように、元々PL/Iという言語は、適用分野ごとにFORTRAN, algol, COBOLが各々利用されていたのを統合しようという野心の元に開発されたとされている。 実際、その仕様には、各々の言語で記述されていた各分野のプログラムが容易に移行できるように工夫したと思われる形跡がある。
“;”を終端記号としたことも、元々はその一環だったのだろうと思われる。 直接には、文の終端記号として“.”を必ず後に置くCOBOLの流儀を継承するのが目的だったのだろう。 それと並んで、行という単位に非常に積極的な文法的意味があるFORTRANからの移行を容易にする効果も狙っていたのではないだろうか。 それは、「“;”は必ず行末に置く」という流儀で記述することによって、文の並び方をFORTRANに似せることが可能になるからである。
しかし、これはalgol系言語の構文規則に対する重大な変更である。 そして、その変更は「改善」に該当すると評価するべきだろう。 実際、B言語がこの流儀を採用したのは、そのように評価したからではないだろうか?
元々、algolは正式名称の“algolithmic language”からも解るとおり、アルゴリズム(処理手順)を合理的に記述するという抽象的な目的で作り出されたものである。 そのため、algolの構文仕様は、抽象的理論的分析が最も容易となる「1次元的な理解」に適合したものとなっている。 algol系言語では「改行位置が意味を為さない」ということがよく言われるが、本来ならば、もう1歩進んだ表現が適切であろう。 即ち、「改行を全部取り払って、横長1列に並べた状態」で理解するのが、algolとしての本来の姿なのである。 この状態では、“;”が分離記号であることは極めて合理的である。
しかし、現実問題として、プログラムを1次元的に記載することは無い。 2次元的(平面的)に、基本的には1文に1行を使って書くし、「段下げ」で「プログラムの入れ子構造を表現」するなどという流儀も推奨されている。 この場合、その文でブロックが終るのか、それとも次行に同格の文が続くのかによって、分離記号の“;”が入ったり入らなかったりする。 即ち、書くときにも読むときにも「次行」が何であるかを意識せねばならないことになる。 プログラムの修正を行う場合、ブロックの最後に文を加除すると、その前行の末尾にも影響が及ぶ。 これは作業として繁雑で面倒なうえに、誤操作の原因にもなる。 “;”を終端記号とした場合には、どんな場合にも常に“;”が入るわけだから、このような問題は生じない。
“;”を分離記号とするalgolの元々の流儀を守っている言語で、後々まで広く使われ続けているのはPascal系の言語である。 しかし、多くのPascalプログラマは、ブロックの最後に冗長な“;”を入れる(即ち、ブロックの最後に「空文」を存在させる)ようである。 分離記号“;”が実用面で劣る文法規則であることは、Pascal系の世界でも認識されているのである。
C言語は開発当初は「NB」(New Bの意)と呼ばれていたらしい。 B言語に対して何が新しいかというと、「データ型の概念を再導入した」ということである。
データ型の概念を再導入した理由の1つは、当時のターゲット機であったPDP-11の機能拡張として近日中に浮動小数点演算がサポートされることになっており、それを素直に扱えるようにすることだったと、開発者自身の論文(文末参考文献参照)で説明されている。 しかし、より重要なのは、この論文でも説明されている、別の理由であろう。
BCPLのような「型の無い」言語が無理無く動作するためには、そのハードウェアにおいてデータを並べる際の自然な「単位」が、データの性質によらず一定でなければならない。 もう少し具体的に述べると、データをメモリ上に並べたときの「並び順」の序数と、データを参照する際に用いる「アドレス」データの値とが同じ単位で変化する必要がある。 つまり、“アドレスの値が1だけ変われば隣のデータを指す”ようになっているということである。 CPUの動作という立場で述べるならば、アドレッシングの単位が基本的なデータの大きさ(ワード)に一致しているということである。
しかし、世の中のCPUは、そういうものばかりではない。 実際、B言語のターゲット機であったPDP-7やC言語のターゲット機であったPDP-11のアドレッシングは、ワードではなくバイト(文字データの大きさ)単位であったらしい。 最近のパソコンで一般的に使われているCPUも同じである。
このようなCPUを前提としたプログラムを「型の無い」言語で記述しようとすると、言語上の単位をどちらかに(現実のB言語ではワード単位に)揃えねばならなくなる。 どちらに揃えても無理や無駄の多いプログラムを書かざるを得ず、出力されるコードにも無理や無駄が多発してしまう。 これは、「計算機の都合」に合わせたプログラミングを指向する言語としては致命的なことだと言えるだろう。
上述の開発者自身の論文では、「文字列操作にライブラリ呼出が必要となり、バイトアドレッシング機の本来の能力を利用した自然な記述ができない」ことと「ポインタ演算の実装に際して、言語レベルでのワードアドレッシングとバイナリレベルでのバイトアドレッシングとの間の変換が逐一必要になる」こととを別の問題として説明しているが、本質的には同じことである。
C言語がBCPLやB言語に無かった「データ型の概念」を導入したということは、その起源は、もしあるとすれば別のところである。 前述した開発者自身の論文では、C言語のデータ型のデザイン全体に関して、algol 68に負うところが多いと述べている。 実際、歴史的発祥のうえでC言語と関連が深いunixのBourne Shellでは、“if”や“case”に対応する「ブロックを閉じるキーワード」に綴りを逆転させた“fi”や“esac”を用いるなど、algol 68の直接的な影響が認められる。
しかしながら、ここでもB言語と同様、主に構文的特徴にPL/Iの影響が認められる。 その顕著な例は、構造体におけるメンバー変数の表現である。 例えば構造体変数“parent”の中のメンバ変数“member”をalgol 68では“member OF parent”と表記する。 これは、COBOLの流儀に倣ったもの(COBOLでは「OF」の代わりに「IN」も可)であるが、C言語では、これを“parent.member”と表記する。 これは、PL/I起源であると考えるのが自然であろう。 Pascalも同じであるが、PascalはPL/Iに倣ったと考えられるし、他の部分にPascalの(algol 68とは異なる内容の)影響が認められないことから考えて、Pascal起源であるとは考えにくい。
C言語が「データ型の概念を再導入した」ということは、B言語がBCPLから受け継いだものを捨て去ったということになる。 しかしながら、それはあくまで表面的なことである。
B言語は、BCPLの「データ型が無い」という特徴を「アセンブラ的発想」でデータを扱うために活用した。 そして、「アセンブラ的発想」はC言語の重要な特徴の1つでもある。 即ち、この発想面において、C言語はBCPLの哲学を継承していると言える。
この哲学が具体的な言語仕様に顕著に反映していると考えられるのが、C言語における「文字データの二面性」「ポインタデータの整数互換性」である。 C言語では「文字型」というデータは整数データである。 即ち、「文字」としての意味を持ちながら、同時にその文字を計算機ハードウェア上で表現する数値(コード)としての意味も有しているのである。 また、C言語の「ポインタ」データは、整数データとの相互変換が保証されている。 通常の整数演算によってポインタデータを処理し、その結果をポインタとして利用することが保証されるという意味で、文字型ほど直接的ではないが、ある意味で「二面性」に近い性質が与えられている。
B言語→C言語の流れを組む以外の高級言語では、このような「二面性」を許容しない。 「文字」や「ポインタ」としての意味を有するデータと「数値」としての意味を有するデータは厳密に区別される。 例えば、ある文字について、その文字のコードに対して演算を行う場合には、ハードウェア上では全く変化が無いにも関わらず、言語表現上は「文字から数値への変換」が要求される。 これは、これらの高級言語が指向する「抽象化」、即ちデータの「意味」をデータの「表現形式」(ハードウェアの都合に依存する部分)から切り離して取扱うという方針からの要請であろう。
計算機の制御動作に密着するような内容のプログラムでは、文字データを表現する数値に対して演算を行ったり、ポインタを「数値」として扱ったりすることは日常茶飯事である。 このような部分でBCPLの哲学を継承することによって、C言語は「アセンブラ的発想」を実現し、「計算機自身の都合」に依存する分野のプログラミングに威力を発揮する結果になったと考えられる。