
mattn:基礎力を付けるのにC言語が向いているかという問に対しては、敢えて「はい」と言いたいと思います。もちろん他の言語でも同様の知識を得る事はできますが、C言語を知った上で
JavaScript や Python や Ruby
を触れる人と、C言語を知らずにそれらの言語を触る人ではコンピュータやプログラミング言語の仕組みの理解度が大きく異なります。
* メモリ構造
* スタック・ヒープ
* GC
言語処理系の実装に必要なこれらこれらを知っているだけで「何故あのプログラミング言語のあの挙動は遅いのか」や「あの言語は凄そうに見えないけど実は凄い事をやっている」といった理解が楽になります。また、なぜ多くのプログラミング言語はC言語で開発されているのか、についても理解できる様になります。
例えば、動的型付型言語の配列は、どんな型の値も代入できるのは何故かという疑問に対して、C言語での言語処理系実装を理解しておくと納得が早いと思います。
動的型付言語の値は、C言語で言うところの union (共用体)
を使って全ての型を表現している事が多いです。以下は、C言語でプログラミング言語を開発する際によく現れる「値」の表現です。
// 型の種別
typedef enum {
TYPE_INT,
TYPE_DOUBLE,
TYPE_STRING,
//...
} VALUE_TYPE;
// 値を表現する構造体
typedef struct {
VALUE_TYPE t;
union {
long i;
double d;
char* s;
};
//...
} VALUE;
値を表現する構造体 VALUE には、型の種類を示す VALUE_TYPE と、その型のそれぞれの値を保持する union で構成されます。union は i,
d, s のメモリを共用していますが、t がある事で i d, s のどれを参照すべきかを判定できます。例えば数値 123 を表示するコードは以下になります。
void
print_value(VALUE v) {
switch (v.t) {
case TYPE_INT:
printf("%ld\n", v.i);
break;
case TYPE_DOUBLE:
printf("%f\n", v.d);
break;
case TYPE_STRING:
printf("%s\n", v.s);
break;
default:
printf("[unknown]\n");
break;
}
}
int
main() {
VALUE number = {
.t = TYPE_INT, .i = 123,
};
print_value(number);
return 0;
}
動的型付言語の配列はこの VALUE
のメモリ列を保持しており、例えば配列の途中の既に格納されている値を異なる型の値で上書きしても配列そのものが壊れる事はない(数値型の配列で実装されている訳ではない)、という理解ができます。そしてなぜ動的型付言語のメモリ使用量は幾分多くなるのか、なぜインタプリタは遅いのか、も理解できます。
こういったプログラミング言語そのものの実装方法や内部構造を理解しておくと、いざそのプログラミング言語での不可解な挙動に遭遇した時に問題の解決ができたり、そのプログラミング言語を使った速い実装を書ける様になる事もあります。
さらにはなぜC言語は間違いを犯しやすく、最近登場する Rust や Go といったプログラミング言語で置き換えられようとしているのか、も理解できるはずです。