01: プログラミング言語とは何か 02: 0から99まで印字するプログラムの解説 03: Pythonで書くと... 04: 変数と関数 05: 条件分岐とループ 06: プリプロセス 07: ポインタと配列1 08: ポインタと配列2 09: ポインタと配列3 0A: 文字列 0B: 多次元配列 0C: ファイル入出力 0D: 構造体 0E: コマンドライン引数 0F: 数値を読み込んで総和と平均を表示するプログラム 10: 数値を読み込んで総和と平均を表示するPythonプログラム 11:【番外編】文章中の単語を出現頻度順に表示するプログラム 12:【番外編】コマンドライン引数を処理するイディオム 13:【番外編】C言語でクロージャを実装する
コンピュータ(機械,マシンとも呼ぶ)が理解する言語は,一種類しかない.機械語(マシン語)だ.機械が異なれば機械語も異なるが,だいたい似たようなものだ.
機械語は,二進数(バイナリ)で書かれているので人間が読むにはつらい.ひとかたまりの二進数に対応するラベル(ニーモニックと言う)をつけて,若干読みやすくしたものをアセンブリ言語と呼んだり,低級プログラミング言語と呼んだりする.ただし,アセンブリ言語は機械語と一対一対応するので,本質的に機械語である.そこで,今後アセンブリ言語も機械語と呼ぶことにする.
人間が理解しやすい言語として,高級プログラミング言語(高級言語)が設計された.コンピュータは高級言語を解釈できないから,二つの流儀が生まれた.すなわち,高級言語を機械語に翻訳するコンパイラと,高級言語を理解する高級コンピュータをバーチャルに構築するインタプリタである.インタプリタはバーチャル機械とも言う.(最初のコンパイラ,インタプリタは低級言語で書かれるのである.)まとめると,以下の二つの系統になる.
・高級言語→(コンパイラが翻訳する)→機械語→(機械が実行する)
・高級言語→(インタプリタ(バーチャル機械)が実行する)
(なぜインタプリタ型コンピュータがリアルに存在しないのかはよい質問である.実際少数ながら存在する.このような高級コンピュータがほぼ存在しない理由は「割に合わない」からに他ならない.)
さて,高級プログラミング言語に関しては,もうひとつ別の軸がある.大雑把に言って,手続き指向,オブジェクト指向,関数指向だ.(この分類はかなり割り切っている.)ここで言う「手続き」指向,「オブジェクト」指向,「関数」指向の「手続き」,「オブジェクト」,「関数」は,プログラミングにおける「手続き」,「オブジェクト」,「関数」とは違った意味だ.コンピュータ科学における用語の混乱のほんの一例である.
手続き指向とは,逐次的な命令を記述するスタイルである.例えば,「ユーザに数値を入力させ,それをxとせよ.xを2倍せよ.xを印字せよ.」と言うようなものである.これは命令指向とも言えるだろう.オブジェクト指向についてはひとまずおくとして,関数指向について述べる.関数指向は伝統的な数学と似ている.先ほどの例で言うと「yとはxを2倍にした量である.ここにxはユーザが入力した数値である.」と言うのである.これは宣言指向とも言えるだろう.(関数指向の言語はインタプリタ系が多い.インタプリタ系は変数を印字せよと命じなくてもいつでも変数の中身を視ることが出来るので,わざわざ印字に関する命令を書いておくことはまずない.)
オブジェクト指向は,手続き指向のバリエーションである.オブジェクト指向では,データとプログラムを分離せずひとまとめに扱う.一方,関数指向の中心的言語であったLispはもともとデータとプログラムの区別がないので,関数指向の場合はそのようなバリエーションは生まれなかった.あえて言えば,関数指向は自動的にオブジェクト指向(風味)と言える.
これでマトリックスが出来る.縦軸にコンパイラ系,インタプリタ系,横軸に手続き指向,オブジェクト指向,関数指向を並べると,こんな感じになる.表の中身は代表的な言語である.複数の領域にまたがる言語もあるが,だいたいの雰囲気はこうである.
手続き指向 | オブジェクト指向 | 関数指向 | |
コンパイラ系 | Fortran, Pascal, C | C++, Objective-C | Lisp, Haskell |
インタプリタ系 | Basic | Java, Python, Ruby, JavaScript | Lisp, Haskell |
高級言語と言えばかつてはコンパイラ型手続き指向のことであったが,現在ではインタプリタ型オブジェクト指向言語が主流であり,今後はインタプリタ型関数指向言語へと移行していくだろう.
ここで,手続きと関数という用語について述べておかねばならない.初期のコンパイラ系手続き指向言語Pascalには,手続きと関数という二つのサブシステムがあった.手続きとはサブルーチン(すなわちプログラムの中の小さなブロック)であり,関数とは「値を返す」サブルーチンである.(Pascalのこうした手続きと関数の区別は,命令と,数学関数のようなものを字面上で区別したかったからだと考えられる.)C言語は手続きを「値を返さない特殊な(あるいはvoid型の)関数」として,手続きと関数を区別しない.またScheme言語は「手続きとは値を返すものである」として関数という呼び方を排除する.手続きも関数もいずれにせよサブルーチンのことである.C言語では,関数とはサブルーチンであり,それは値を返すかもしれないし,返さないかもしれないと覚えておけばよい.
ちなみに,プログラムの本体ですら,C言語ではmainという名前の「関数」である.関数であるから,何か値を返す.現在主流の実装ではmain関数は整数を返すことになっている.(以前はvoidすなわち値を返さないmain関数も認められた.)main関数は値をどこに返すかというと,プログラムを起動したプログラム,すなわちシェルに返す.これは ./test1 && ./test2 のようにプログラムを連続して起動させる場合に便利である.(C言語のmain関数のreturn xはexit(x)関数呼び出しと同等であるため,exit関数によるプログラム終了のほうが機会は多い.)
高級言語は,コンパイラ系にしろインタプリタ系にしろ,ある程度,実際のコンピュータを抽象化したものと言える.抽象化とは細部を切り捨てることであるから,実際のコンピュータないし機械語のいくらかの機能は高級言語で書かれることはない.例えば,現在のほとんどの高級言語では「ポインタ」という概念は切り捨てられている.ポインタは本来機械語レベルの概念で,あるデータまたはプログラムがメモリ(コンピュータの一部)の中の「どこにあるか」を指す概念である.
特にC言語は高級言語としての抽象化の度合いが低い(C言語を低級言語と呼ぶプログラマもいる)ため,ほぼ機械語のポインタと同じ概念の抽象ポインタがある.(これも用語混乱の例であるが,機械語世界ではポインタをベクタと呼ぶ.)
これから話をC言語に絞ろう.C言語はもはや時代遅れで,C言語を使った開発などかなり特殊なケースであるが,大学ではいまだに教えられているし,また多くのプログラマの第二言語でもある.
C言語の解説に入る前に,ひとつ誤解を解いておこう.C言語は特定のコンピュータに依存しないという建前ではあるが(C言語の聖典「プログラミング言語C第2版」にそうかかれている),はっきり言ってそれは冗談だ.コンピュータごとの差異をコンパイラが吸収するというのは建前論であって,C言語で書かれたプログラムはたいがいコンピュータの機種に依存することになる.(一方インタプリタ系言語は本当に機種依存しない.)
C言語はコンパイラ系言語であり,C言語で書かれたプログラムはCコンパイラによって機械語(バイナリ)に変換される.しかし,ちょっと待った.Cコンパイラは,実際にプログラムをコンパイルする前に,プリプロセスと呼ばれる処理をプログラムに施す.プリプロセスは,C言語のソース(おおもとのプログラム)から,コメントを取り除き,#で始まる行(プリプロセッサ指令)を読んでいって,ソースを適切に書き換える.
一番よく使われるプリプロセッサ指令のひとつに,
#define N 100
のようなものがある.これは,「この行以降の,ソースの中のNをすべて100という文字で置き換えなさい」という意味である.また,おそらく一番よく使われるプリプロセッサ指令は
#include <stdio.h>
であろう.これは「この行をstdio.hという名前のファイルの内容で置き換えよ」という意味である.Macの場合stdio.hファイルは/usr/includeというディレクトリ(ファインダからは見えない)に置かれている.多くのUnixシステムでも同様であろう.
プリプロセッサ指令が実際どう働くかはこうすればわかる.シェル(Macの場合はターミナルとほぼ同義語)の上で,ソースコードをsource.cとすると,
$ gcc -E source.c
とすれば,画面にだーっとプリプロセス後のソースコードが流れる.もしファイルに残しておきたければ,
$ gcc -E source.c > preprocessed.c
とすればよい.preprocessed.cは依然C言語であるから,そのままコンパイル可能である.(Cコンパイラとして GNU Compiler Collection (gcc) が使えることを仮定した.)
ソースコードのコンパイルは
$ gcc source.c
とする.機械語ファイル(実行ファイル,バイナリファイル,バイナリとも言う)は勝手にa.outという名前になる.機械語ファイルの名前を指定しておきたければ,
$ gcc -o binary source.c
とする.注意!夜遅くにコンパイルをすると,うっかり
$ gcc -o source.c source.c
などとやってしまう.これはsource.cをコンパイルした内容でsource.cを書き換えてしまう.エディタに内容が残っていなければせっかく書いたプログラムは失われてしまう.気をつけること.(Makeというメカニズムを利用するとコンパイルにまつわる諸々の作業を自動化できる.)