C言語の変数とメモリとアドレスの話

Cの入門などでは変数の説明で箱をイメージさせることがたびたびあるが、ポインタのところまで来ると箱の名前だとか場所だとか住所だとか言われていまいちピンとこない。ポインタが理解できない一因はこの「箱のイメージ」なのではないかと思う。
変数とは言語上のデータを配置する何かを概念化したものであって、物理的に存在するものでもないし、例えば実行コード上には影も形も無かったりするかもしれない。それをポインタという、CPUがアクセスするアドレスに依存する、いわば実在する何かと関連付けるときに、概念レベルで食い違いが発生するのである。などとテキトーなことを言いつつ。本気にしたらあかんよ?書いてて自分でもほんまかって思ったし。

さて、今回はメモリとアドレスについて。
C言語高級言語ではないから、CPUとメモリチップがどのようにやりとりしているのかというあたりのイメージがあると理解しやすい。かもしれない。DRAMインターフェイスとかそういう難しい話ではなくて、ソフト屋はたぶん触れることがないであろう回路的な話。だけど回路屋に見られたら鼻で笑われそうな、そんな感じの。

CPUとメモリチップの接続

死ぬほど簡略化した図を用意した。

CPUは普通のCPUとするが、線が2本しか出ていないので普通ではない。気にしない。メモリチップは1bitの情報を蓄えることができ、CPUから書き込まれた情報を保持し、返すことができる。
見てのとおり、CPUとメモリが2本の線で接続されている。これは、見たまま2本である。例えばこの線に5Vか0Vの電圧がかかるとしよう。5Vのときはデジタルの1を表し、0Vのときは0を表す。そういう感じで。
W/Rってのは読み書きを表す信号線で、ほんとはWの上に線を書きたかったのだが、面倒だったので無い。この信号はCPUが出力し、0のときに書き込み、1のときに読み込みをしたい、という要望をメモリチップに伝える信号である。
データは1bitの情報を相互に送受信するための線である。いわゆるデータバス。W/Rが0の時にはCPUが出力してメモリが入力するし、1のときにはメモリが出力してCPUが入力する。両方同時に出力すると物理的にぶっ壊れるのでそういうことをしないように注意して設計する。

これがどう動くかというと、CPUがメモリにデータを書き込みたいときにはW/Rを0にしてデータバスにデータを流す。逆に読み込みたいときにはW/Rを1にしてデータバスから入ってきたデータを受け取る。説明するまでもなかった。

アドレスバスを追加する

保存できるのがデータ1bitだけでは何かと不便なので、容量を倍に増やしてみよう。

真ん中にアドレスバスが増えた。他の2本はさきほどと同じである。
この3本インターフェイスに対応したメモリチップはデータを2bit持つ。アドレスバスはCPUが出力し、メモリチップはそのアドレスに対応したデータを読み書きすることができる。アドレス0で書き込んだデータはアドレス1のデータに影響しないし、その逆も同じ。独立したデータとして保持する。データバスは1本なのでどっちか片方ずつのアクセスとなる。

動きとしては、例えばアドレス0に1を書き込みたいときは、CPUがW/Rを0にしてアドレスを0にして、更にデータを1にする。そうするとメモリチップはアドレス0のメモリにデータバスの1を、W/Rに従って書き込む。アドレス1を読み込みたいときはCPUがW/Rを1にしてアドレスを1にする。そしたらメモリチップがそれに従ってアドレス1のデータをデータバスに出力するから、CPUがそれを読み込む。
実際には電源投入直後のメモリの状態は不定なので、書き込む前に読み込むと何が出てくるかわからない。メモリの内容はきちんとまず書き込みしてから使う。

アドレスとデータと変数

なんとなくアドレスとデータというものがわかってきただろうか。
メモリの中には保存する場所がいっぱいあって、そのどこをアクセスするかを指定するのがアドレスであり、そこに格納されている値がデータである。
C言語の変数は基本的にはメモリに配置される。されないこともあるが、そういう話を排除するならば、変数とはコンパイラやOS、CPUによって自動的にアドレスが決定されるメモリである、と言える。変数への書き込みはインターフェイスを通じてメモリに書き込まれるし、変数からの読み出しは同様にメモリからデータを取得する。
そして、Cのポインタというのはこの変数の格納先のアドレスを保持するための変数である。上記の図で言うなら、ポインタ変数に入っている値はそれをアドレスバスに出力する形で使うことになる。

さらに拡張すると

ここで挙げたちょー簡単な回路図だが、例えばこいつのデータバスを8本にすると、同時に8bitの読み書きができるようになる。この状態では、アドレス1つに対して8bitの情報を保持することができる。隣のアドレスには違う8bit保存されている、というイメージ。いまどきのPCはこの状態である。
また、更にアドレスバスを32本にすると4GByteのデータを保持することができるようになる。いわゆる32bitOSはRAMが4GByteまでってのは、こういう話である。
あとは、隣り合ったメモリを同時に読み書きできるようにデータバスをもっと増やすとか、1チップに4GByteは無理があるからメモリチップをわけてアドレスデコーダを追加するとか、アドレスバスの下2bitを落とすだとか、していくと、いまどきのPCっぽくなっていくが、まあ、それこそどうでもいい話。

おしまい

これで何かが理解しやすくなるのかどうかはさっぱりわからないが、何かしらの一助になれば幸いである。
低レベルのコードを書くならメモリとアドレスとデータのイメージは持っておかないと厳しいと思う。箱じゃなくて。