もっと早く知りたかった...。C言語の話
対象読者
解決すること
内容
C 言語のコンパイラを作成してみて、言語とコンパイラについての理解が進んだと思ったので、C言語学びたての自分がこう教ええもらってたらなぁ、というのを幾つか書きたい。
早く言ってくれよ!ってやつです。書いてあったのに私が分かってなかっただけなのかもですけども。
まず知りたかったこと。それは...
結局場所と大きさだ!という話。
名前の指定も場所、つまりアドレスの指定なので名前も含む。大きさは型。メモリの指定方法を色々あーだこーだやってるとまとめてくれたら、もっと分かりやすかっただろうにな、と思う。
例えば、#include<stdio.h> は、最初おまじないの様に扱われ、次に他のファイルを読み込むと学ぶけれども、そのファイルの中身については「えらいひとがつくったもの」位にしか分からないだろう。定数の定義や prototype が入っていて、prototype が何故必要なのかをメモリの配置から教えてくれれば、もっと脳ミソのストレスが減ったんじゃなかろうか。
prototype も、結局のところ名前からオブジェクトファイル中の関数定義を探し出し、引数の並びと型によってデータの渡し方とその大きさの指定をしているだけなんだか、長いこと「そういうものだ」ではストレスだと思う。
構造体も、各メンバは結局先頭アドレスからのオフセットで表される。メモリに詰め込まれる全体の位置がその構造体の名前で示される。パッディングはあっても先頭のメンバのオフセットはいつも0。
全部そうやって処理される。C言語は低レイヤな構造と結びついているから、どーせならそっちを先に説明してくれた方が早かったんじゃないかと思う。
続いて、
スタック構造(とヒープ)
スタックフレームとヒープについては、もう初めから説明あってもいいんじゃなかろうか。私がよく間違えていたのは、配列のローカル変数がその関数外に出ると良く分からない挙動になって現れるってやつ。ローカル変数は関数から出ればメモリの割当が外れるけども、数値などには値渡しがある。しかも構造体も値渡しだ。何故初めの方に出てくる配列が駄目なのか..。しかもエラーは吐かない。
これも、スタックフレームとヒープを説明してれれば、すんなりと理解出来たんじゃないかなぁと思う。
それに、スタック構造ってスンバラシイし、C言語って結局関数スタックの折りたたみ?みたいのの威力が大きいと思うので、早めに説明受けたかったなと。
あと、
グローバル変数のメモリ構造
以下みたいなので時間食われたりしました。
char *str0 = "hello\n";
char str1[] = "hello\n";
int main(){
str0[0] = 'H'; // error ! dumps core
str1[0] = 'H'; // no error
}初心者でもこの手のコードは書くと思う。でも、これが何故駄目なのか分かるのってかなり後でした。
関数内の変数に static 付けた際の挙動とかも、普通に規則の側から学ぶと例外的な扱いとして覚えることになると思うのだけれど、大域変数のメモリ構造とそれへの割当を学べば、かなりすんなりと入ってきたハズ。
んで、
配列の話
これは他の場所でも書きました。
最初の頃なんて、配列とポインタは同じ様に扱えると書いてあるのに何故配列にポインタを代入出来ないのか、さっぱり分かりませんでした。配列とポインタを同様に扱えるってのは、誤解を招く書き方なんじゃなかろうか。
コンパイラ作る前も結構こういう所に苦労して、本に当たったりしましたけどあまり参考になるものはなくて。一応私が参考になったと思った本も載せておきます。だいぶ古いですけど...。
エキスパートCプログラミング: 知られざるCの深層 (アスキーブックス)
他に思い出したらまた書きます。