高級言語、低級言語

前講「不動点、浮動点」は、浮動小数点の演算をハードウエアが引き受けるようになったのは重要な前進だが、 それよりもっと大きな出来事——コンピュータ言語の発展——が1950年代前半にはすでに始まっていた、という 一言で幕を閉じた。その大きな出来事を追うのがこの講である。 プロセッサが直接理解できるのは、メモリに並んだ0と1だけである。 そこから人間の言葉に近づけていくには、どんな階段を上ればよいのか。略記号を与え、変換をコンピュータに 任せ、やがて代数の式そのものを書けるようにする——マシンコードから高級言語までの3段の階段を、 1段ずつ上っていく。全5セクション・演習22問。回答はブラウザに保存されるので、途中でやめても 続きから再開できる。

進捗 0 / 22 問(正解 0 問)

1. マシンコードとニーモニック — アセンブリ言語への第一歩

プロセッサが直接理解できるのは、メモリに並んだバイトの列だけである。しかもその1つ1つの命令は「メモリから数を読み込む」「他の数に加える」「結果をメモリに戻す」といった、極めて細かい単純作業に限られている。人間がこれを直に書き下ろすのは、途方もない量の手順を積み上げる作業になる。そこでまず用意されたのが、各バイトに対応する略記号——ニーモニックだった。MOV・ADD・CALL・HLTのような英語に近い記号でマシンコードのバイトを指し示せば、少なくとも読めるものになる。ただし書き終えた後には、それを1つずつバイトに置き換える作業が待っている。人間の手でこれをやるのがハンドアセンブルである。

マシンコードを直接書くのは、控えめに言っても煩雑である。理由は単純で、 1つ1つの命令が「メモリから数を読み込む」「他の数に加える」「結果をメモリに保存する」といった、 極小単位の単純作業に限られているからだ。何かまとまった仕事をさせようとすれば、その最小の 動作を膨大な数だけ積み重ねるしかない。しかも積み重ねるのは、意味を読み取りにくいバイトの並びである。

そこで最初の一歩として用意されるのがニーモニック(略記号)である。マシンコードの 各バイトには、MOV・ADD・CALL・HLTのような所定の略記号が対応づけられており、そこにオペランド(命令が 操作する対象)を添えれば、命令を英語に近い形で指し示せる。ニーモニックはバイトそのものではなく、 そのバイトが表す命令の別名である。そして重要なのは、 ニーモニックとマシンコードの間には1対1の対応があるということだ。

この略記号を使って書いたプログラムがアセンブリ言語のプログラムである。ただし書いた だけでは動かない。マイクロプロセッサが実行できるように、1行ずつマシンコードのバイトへ置き換える作業が 残っている。これを紙の上で人間の手でやるのがハンドアセンブル——コンピュータの助けを 借りない、最も原始的な変換方法である。

ハンドアセンブルは退屈な作業だが、機械的に進められるという美点がある。命令ごとのバイト数が決まって いるので、プログラムが最終的に何バイトになるかは、命令を並べた時点で足し算だけで求まる。この 「機械的に決まる」という性質こそが、次のセクションで変換をコンピュータに任せられる理由になる。

演習 1理解

マシンコードを直接書いてプログラムを組み立てることが煩雑とされる最大の理由はどれか

演習 2理解

マシンコードの各バイトには、人間が読みやすい略記号(ニーモニック)が対応づけられている。ニーモニックの役割の説明として正しいものはどれか

演習 3理解

アセンブリ言語で書いたプログラムを、コンピュータの助けを借りずに人間の手で1つずつマシンコードのバイト列に変換する作業のことを何と呼ぶか

演習 4計算

自作の例として、あるプロセッサでは命令Xが3バイト、命令Yが2バイト、命令Zが1バイトのマシンコードに変換されるとする。X→X→Y→Zの順に4つの命令を並べたプログラムをハンドアセンブルすると、生成されるマシンコードは合計何バイトになるか

このセクション: 0 / 4 問正解

2. アセンブラの仕事 — 自動変換とその限界

ニーモニックとマシンコードの間には1対1の対応がある。ならばその変換自体をコンピュータにやらせればよい——そのためのプログラムがアセンブラである。アセンブラはアセンブリ言語で書かれたソースコードを読み込み、機械語を含むファイルを書き出す。対応表と照合するだけなので比較的単純なプログラムで済み、そのうえラベルの実アドレスまで自動で計算してくれるので、プログラマは番地を自分で数えなくてよくなる。新しいプロセッサ向けのアセンブラを既存のコンピュータ上で書くクロスアセンブラという技も生まれた。それでも2つの問題は残る——単調さと、移植性のなさである。

ハンドアセンブルが機械的にできるなら、その機械的な作業自体をコンピュータにやらせればよい。そのための プログラムがアセンブラである。アセンブラはアセンブリ言語で書かれたソースコードを 読み込み、機械語を含むファイルを書き出す。単純な環境では、その出力がそのまま実行 できる形式になることもあるが、一般にはオブジェクトファイルであり、リンカによる結合・再配置を経て 実行可能ファイルになる。

アセンブラが比較的単純なプログラムでいられるのは、S1で確かめた 1対1の対応のおかげである。テキストの各行を略記号と引数に分け、可能な略記号・引数の 一覧と照合するだけでよい。そのうえアセンブラは、命令文が参照するラベルの実アドレスも 自動的に計算してくれる。各命令が何バイトかを先頭から積算していけば、ラベルが何番地に来るかは決まる からだ。

先頭 0300h + 命令6バイト分 = ラベルの位置 0306h

プログラマが番地を自分で数えなくてよくなる、というのは想像以上に大きな解放である。命令を1つ挿入する たびに、以降のすべてのアドレスを書き直す必要がなくなるからだ。ちなみに最初のアセンブラは当然 ハンドアセンブルで作るしかないが、いったんアセンブラが手に入れば、新しいアセンブラはアセンブリ言語で 書いて既存のアセンブラに処理させられる。新しいプロセッサが登場したときも、既存のコンピュータの上で そのプロセッサ用のコードを作るクロスアセンブラ(コンピュータAで実行されるが コンピュータB用のコードを作るアセンブラ)を書けばよい。

それでもアセンブリ言語には、2つの大きな問題が残る。1つは、 非常に単調でプロセッサレベルの細部まで気を配らなければならないこと。もう1つは、 移植可能でないこと——あるプロセッサ用に書いたプログラムは、別のプロセッサでは まるごと書き直しになる。アセンブラはこの2つを解決しない。解決の道は、階段をもう1段上ることにある。

演習 5理解

アセンブラの基本的な仕事の説明として正しいものはどれか

演習 6計算

アセンブラは、命令文が参照するラベルの実アドレスも自動的に計算してくれる。自作の例として、プログラムの先頭アドレスが0300hで、そこから合計6バイト分の命令を並べた直後にラベルDATAが置かれているとき、DATAのアドレスはどれか(16進数の足し算)

演習 7理解

アセンブラを使うようになっても、アセンブリ言語プログラミングには依然として大きな問題が2つ残る。その組み合わせとして正しいものはどれか

演習 8理解

あるコンピュータAの上で実行されるが、コンピュータBのためのマシンコードを生成するアセンブラのことを何と呼ぶか

このセクション: 0 / 4 問正解

3. 高級言語の誕生 — コンパイラという新しい翻訳者

細かい命令を1つずつ並べるのをやめて、伝統的な代数の表記で計算を書けたらどうか。この発想が意味を持つのは、同じ式を多数の異なる値について繰り返し評価したいときである——1回きりの計算なら電卓で足りる。こうして生まれたのが高級言語であり、ハードウエアに非常に近いアセンブリ言語だけが低級言語と呼ばれ、それ以外の言語は広く高級言語と呼ばれるようになった。ただし言語を定義しただけでは動かない。命令文をマシンコードへ翻訳するプログラム、コンパイラが要る。アセンブラの1対1対応とは違い、コンパイラは1つの命令文を複数のマシンコード命令へ展開する——だからこそ書くのが難しい。

初歩的な算術演算のマシンコード命令を1つずつ並べるのをやめて、 伝統的な代数表記で数学の演算をそのまま書けたらもっといい——この発想が高級言語の 出発点である。ただし、それに意味があるのは、同じ式を多数の異なる値について評価する必要があるときに 限られる。1回きりの計算なら電卓で足りるからだ。変数を持つ式を書き、そこに値を次々に流し込む—— コンピュータにやらせる価値があるのは、そういう仕事である。

こうして高級プログラミング言語が生まれる。アセンブリ言語はハードウエアに非常に近い ため低級言語とみなされ、それ以外のあらゆる言語が広く高級言語と 呼ばれる。もっとも、これは絶対的な線引きではない。高級さの度合いは言語によってかなり違う。

言語を定義しただけでは、当然ながら何も動かない。その言語で書かれた命令文をマシンコードに変換する プログラム——コンパイラが要る。コンパイラもアセンブラと同じくソースコードを1文字ずつ 分解していくが、決定的に違う点がある。アセンブラが1対1で対応させるのに対し、 コンパイラは通常、1つの高級言語の命令文を複数のマシンコード命令に翻訳する。 1行が4命令にも10命令にも展開されうる。だからコンパイラは書くのが容易ではなく、その設計と構築だけで 専門書が何冊も書かれている。

高級言語の長所は3つある。学びやすくプログラムしやすいこと。プログラムが明確・簡潔になりやすいこと。 そして普通は移植可能であること。ただしこの3つ目には、はっきりした限定条件がつく。 移植可能なのはソースコードであって、実行可能ファイル自体は依然として個々の プロセッサに依存する。複数のプロセッサで動かしたければ、それぞれ専用のコンパイラが必要になる。

代償もある。優秀なアセンブリ言語プログラマの方が良いコードを書けることが多く、生成される実行可能 ファイルは大きく遅くなりがちである(近年は最適化コンパイラの進歩でこの差は縮まっている)。さらに 原理的な限界として、高級言語はプロセッサを使いやすくするが、より強力にするわけではない。 多くのプロセッサが持つビットシフト命令やブール演算を取り込んでいる高級言語はほとんどなく、真に 移植可能であろうとする言語ほど、プロセッサ固有の機能からは遠ざかる。

抽象化の階段(マシンコード → アセンブリ言語 → 高級言語)段の間にいるのが変換者。アセンブラは1対1、コンパイラは1対多で下の層へ変換する高水準(人間に近い)低水準(プロセッサに近い)3段目(最上段・高水準)高級言語人が読み書きする1つの命令文a = 5;自作の代入文2段目(中段)アセンブリ言語ニーモニックで書いた1命令MVI A,5Aに即値5を転送する命令1段目(最下段・低水準)マシンコードプロセッサが直接実行する0と100111110 0000010116進では 3E 05コンパイラ1文 → 複数命令アセンブラ1命令 → 1命令1段目と2段目の間には1対1の対応があるが、3段目の1文は複数の命令へ展開される——ここが階段の段差の正体である

この「1文 → 複数命令」の展開は、行単位で突き合わせると実感しやすい。下のシミュレータで 高級言語側の行をクリック(タップ)すると、その1行から生成されるアセンブリ言語の命令行が ハイライトされ、逆にアセンブリ言語側の行をクリックすると、どの1行から来た命令かをたどれる。 高級言語側は次のセクションで扱うALGOL風の表記(代入は :=)、アセンブリ言語側は 上の図と同じ単純な命令の典型像である。

さわって確認コンパイラの対応表 — 1つの命令文が何命令に展開されるか

行をクリック(タップ)すると、対応する相手側の行がハイライトされます

高級言語(ALGOL風)
アセンブリ言語

宣言文 integer a; は、変数の名前と種類(整数)をコンパイラに知らせるだけで、マシンコード命令には展開されません。いっぽう代入文 a := 5; は「アキュムレータAに即値5を転送する」「Aの値を変数aの番地へ保存する」の2命令になります。変数の名前はコンパイル後には残らず、ただのメモリの番地になります。

演習 9理解

個々のマシンコード命令をすべて捨てて、伝統的な代数式のような表記でプログラムを書きたいという発想が生まれた理由として正しいものはどれか

演習 10理解

「低級言語」「高級言語」という呼び方の説明として正しいものはどれか

演習 11計算

コンパイラはアセンブラと違い、1つの高級言語の命令文を複数のマシンコード命令に翻訳する。自作の例として、ある代入文がコンパイルの結果4つのマシンコード命令に展開され、各命令が2バイトだとすると、この1文に対応するマシンコードは合計何バイトになるか

演習 12理解

高級言語の長所として正しいものはどれか

演習 13理解

高級言語の代償として正しいものはどれか

このセクション: 0 / 5 問正解

4. ALGOLで見る高級言語の要素

高級言語がどんな部品でできているのかを、ALGOLを例に見ていく。ALGOLはどの企業の製品でもなく国際委員会が設計した言語で、その後の多くの言語の直接の祖先になった。ブロック構造、実数と整数を区別する宣言文、等号と区別された代入の記号、値を数え上げるループ、条件によって道を分ける文、番号で要素を指し示す配列——今日のプログラミング言語で当たり前になっている道具立てが、ここでほぼ出そろっている。中でもtrueとfalseの2値だけを持つブール型は、第5講で扱った19世紀の論理学が、そのまま言語の型として組み込まれたものである。

高級言語の中身を具体的に見るために、ここではALGOL(ALGOrithmic Language)を例に 取る。特定の企業の製品ではなく国際委員会が設計した言語であり、その後の多くの汎用言語の直接の祖先に なったので、どの言語にも肩入れせずに「高級言語の要素」を眺めるのに向いている。

まず、プログラムの範囲はbeginendで囲まれる(ブロック構造)。 変数は使う前に宣言文で種類を告げる——実数ならreal、整数なら integer。これによってコンパイラは、変数の名前とその種類、さらに保存に必要なメモリの 大きさを知ることができる。値を代入するときは:=という記号を使い、等号 (比較に使う=)とはっきり区別する。

繰り返しはfor文で書く。値を並べて列挙することもできるし、初期値・増分 (step)・上限(until)の3つで反復を制御することもできる。

for k := 4 step 3 until 22 do ...

この場合、kは4から始まり、3ずつ増えながら22を超えるまで繰り返される。条件によって道を分けるときは ifthenelseを使う。条件が真ならthenの後の文を、 偽ならelseの後の文を実行する——今日のほとんどの言語に受け継がれている形である。

同じ種類の値をまとめて扱うときは配列(array)を宣言する。角括弧の中の数が 添字(サブスクリプト)で、要素を指し示す番号になる。宣言した添字の範囲が、そのまま 要素数を決める——たとえば範囲が5から20までなら、要素は16個である。

そしてALGOLには、trueとfalseの2つの値だけを持つ型もある。名前はブール型—— 第5講「論理とブール代数」で扱った ジョージ・ブールにちなむ。19世紀の論理学者が組み立てた代数が、リレーの回路を経て、 今度はプログラミング言語の型として姿を現している。ここまで来た道のりを思い出すには、ちょうどいい 目印である。

演習 14理解

ALGOLでは変数を使う前に「real a;」「integer i;」のように型を宣言する。この宣言文の役割として正しいものはどれか

演習 15計算

自作のALGOL風for文「for k := 4 step 3 until 22 do ...」は、変数kを4から始め、22を超えるまで3ずつ増やしながらブロックを実行する。このループは合計何回実行されるか

演習 16理解

ALGOLの条件文「if a < 0 then(文1)else(文2)」の働きとして正しいものはどれか

演習 17計算

自作の配列宣言「real array x[5:20];」の添字の範囲は5から20までである。この配列が持つ要素の個数はいくつか

演習 18理解

ALGOLは「true」と「false」の2つの値だけを持つ型も宣言できる。この型の名前と、その名の由来になった人物の組み合わせとして正しいものはどれか

このセクション: 0 / 5 問正解

5. 高級言語の系譜 — FORTRANからオブジェクト指向前夜まで

最初の真の実用コンパイラは、1952年にグレース・ホッパーがUNIVAC用に作ったA-0とされる。今も使われる最古の高級言語は、科学技術計算向けにIBMが開発したFORTRANである。以降、ビジネスのレコード処理に向けたCOBOL、初学者に向けたBASIC、ALGOLの構造を受け継いだPascal、そしてオペレーティングシステムを書くために生まれたCと、目的の異なる言語が次々に登場した。一方で、広く使われている言語のほとんどはフォン・ノイマン型コンピュータを前提に設計されている——そこから外れた設計としてLISPとAPLがある。そして章の最後には、ALGOLに似た言語群にオブジェクト指向という強化が加わる、という次講への予告が置かれている。

最初の真の実用コンパイラは、1952年にレミントン・ランド社のグレース・マレー・ホッパー (1906〜1992年)がUNIVAC用に作ったA-0とされる。ホッパーは1944年にハワード・エイケン の下でも仕事をし、80歳を超えても業界で働き続けた人物である。

現在も使われている最古の高級言語はFORTRAN(FORmula+TRANslationから)。1950年代に IBMが704シリーズ向けに開発し、浮動小数点演算や複素数を手厚くサポートしていたことから、科学者・技術者 に長く好まれた。これに対しCOBOL(COmmon Business Oriented Language)は、1959年に 米国産業界と国防総省の合同会議で開発が始まったビジネス向けの言語で、ホッパーの影響を受けている。 レコードやレポートの処理を得意とし、年号を2桁で扱う当時の慣習は、後の「ミレニアムバグ」の遠因に なった。

BASIC(Beginner's All-purpose Symbolic Instruction Code)は、1964年にダートマス大学 のジョン・ケメニーとトーマス・クルツが、数学・工学を専攻していない学生のために開発した。変数の型を 指定しなくてよく、多くの実装は「コンパイラ」ではなくインタプリタ——ソースコードを 読んで実行可能ファイルを作らずに直接実行する方式で、コンパイルした場合より遅くなる傾向がある。 1975年にビル・ゲイツとポール・アレンがAltair 8800用のBASICインタプリタを開発し、マイクロソフトを 設立した。

ALGOLの構造とCOBOLのレコード処理を受け継いだのが、1960年代後半にスイスのニクラウス・ヴィルトが設計 したPascalである(1983年にBorlandが49.95ドルで出したTurbo Pascalが、統合開発環境 とともにその普及を後押しした)。米国防総省向けに作られたAdaもPascalの影響を受けた 言語で、その名はバベッジの解析エンジンの記録者オーガスタ・エイダ・バイロンに由来する。

Cは1969〜1973年にベル電話研究所のデニス・M・リッチーが主に作った言語で、CPL・BCPL・ Bという系譜の先にある。1973年にUNIXがCで書き直されて以来、このOSと言語はほとんど一体のものとして 扱われてきた。begin/endの代わりに中括弧を使い、簡潔な記法を持つ。そして 多くの高級言語が省いているビットシフト演算やブール演算、さらにポインタ(数値メモリアドレス) をサポートしている——一般的なプロセッサの命令セットをよく真似ているため、 「高級アセンブリ言語」と呼ばれることさえある。

ここまで挙げた言語(=一般に使われる言語のほとんど)は、ALGOLに似ており、いずれも フォン・ノイマン型コンピュータを前提に設計されている。そこから外れた設計として、 1950年代後半にジョン・マッカーシーが作ったLISP(List Processing)と、同じころ ケネス・アイヴァーソンが作ったAPL(A Programming Language。数値配列全体への同時演算 を独特の記号体系で書く)がある。

ALGOLに似た言語が支配的である状況は今も続いているが、近年、そこに オブジェクト指向という強化を加えた言語が登場し、グラフィカルなオペレーティング システムでの仕事に役立つようになった——第24章はこの一文で、次の講へとバトンを渡す。画面に絵を描き、 マウスで操作するコンピュータがどう生まれたのか。それが最終講の主題である。

高級言語の系譜図(年代の並びと影響関係を分けて見る)上段は登場年を並べた帯・下段が継承関係。同じ時期に出たことは継承を意味しない上段:年代タイムライン(登場年の位置に並べるだけ・言語間は線でつながない)ALGOL 58 / 60 / 681957〜68年・国際委員会FORTRAN1950年代・IBMBASIC1964年・ダートマス大学C1969〜73年・リッチーCOBOL1959年・ビジネス向けPascal1960年代後半・ヴィルト登場年1955196019651970オブジェクト指向言語(次講)ALGOLに似た言語群への強化下段:影響関係(上段の年代の並びとは別に扱う)ALGOL国際委員会Pascalヴィルト構造を直接継承ALGOLからCOBOL・BASICへは直接の継承線を引かない(間接的な影響に留める)C言語の系譜(上段のCとは別に示す・ALGOLとは別系統)CPLBCPLBCフォン・ノイマン型から外れた設計LISP(1950年代後半)ジョン・マッカーシーAPL(1950年代後半)ケネス・アイヴァーソン上段は登場年を示す帯であり、並びの近さは継承関係を意味しない。継承関係は下段の矢印だけが表す
演習 19理解

次の言語とその性格の組み合わせのうち、すべて正しいものはどれか

演習 20理解

ALGOLについての説明として正しいものはどれか

演習 21理解

C言語の特徴として正しいものはどれか

演習 22理解

LISPとAPLに共通する特徴、およびこの章の末尾が予告する次の話題として正しいものはどれか

このセクション: 0 / 4 問正解