ポインタ
まずは、「C言語には、メモリに直接アクセスする命令はない!」と理解する。
たとえばC言語に「write_mem8(0x1234,0x56」なんて命令があればいいのだが・・・
そこで、C言語ではポインタというものを使って、メモリの番地を直接扱えるようになっている。
たとえば
char p; p = 0x41;
なんてしたとしよう。
ここまではまあ、大抵の人は意味解るはず。
さて。ここでこう考えてみる。「pの中には0x41が入っている。じゃ、0x41が入っているのって、どこ?」ということ。
「はぁ? 変数pだべさ?」と。これは正解。では、「その変数pって、どこ?」と、こう続く。
「変数pは変数pだろう!」というわけには行かない。なぜなら、コンピュータではどんなデータも変数もメモリに記憶されている。記憶されているのがメモリである限り、そこにはかならず「何番地か?」という概念が付いてくる。
(ちなみに、これを意識する必要がないという場合のほうが多い。そういう場合はもちろん意識しなくてもよい。)
やりたいことを先に考えてみる
C言語とて普通の場合はアドレス変数(ポインタ)などほとんど使わない。というか、使わなくても大抵のことは実現できることを忘れてはいけない。
「ポインタを理解していないとCは使えない」は正しくない。代替手段を工夫したり関数を探してきたりすれば大抵解決できることを忘れてはいけない。もしC言語の入り口でポインタに遭遇し、運悪く躓いてしまったら「とりあえずはシカト」で先に進むことをお勧めする
さて、こういう場合を考えてみよう。
- 「VRAMを直接操作して、画面に文字を書きたい」
- AT互換機の場合、メインメモリの一部にVRAMが接続されている。これはグラフィックモードはもちろんだが、テキストモードでも同じである。(普通はB800番地〜BFFFF番地の範囲)
- C言語のprintf等を使って文字を出力することは可能だが、画面にAA(アスキーアート)で絵を書きたいなんて場合は直接書いたほうがずっと楽だし応用も効く。
- とにかく、0xB8000に0x41を、0xB8001に0x42を、0xB8002に0x43を・・・と書き込みたいと考える。
さてその場合。C言語でできるだろうか?
ふと想像すると、C言語のどこかに 「 writemem(0xB8001,0x42);」 なんて命令がありそうである。
・・・しかし残念ながら、そんな命令はない。(少なくとも標準関数では。)
では、Cではある番地にある値を代入することはできないのだろうか?。あるいは「そういう操作や考えをしてはいけない言語」なのだろうか???
そこで登場するのが、アドレス変数(ポインタ)である。
まずはとにかく、以下を見てほしい。これはまったく同じ結果になる。
writemem(0xB8001,0x42); ←妄想した関数。こんなのは実際にはない。しかし この関数がもしあったなら、なにをしてくれる 関数かは解るだろう。メモリの0xB8001番地に、 0x42を書き込めと命令している。 char *p; ←上記の妄想関数と同じことをC言語のポインタ p = 0xB8001; でやろうとするとこうなる。まずはとにかく、 *p = 0x42; この3行で一組と考えるとわかりやすいかも。
逆を考えてみよう。 メモリの0xB8003番地の値を読み出して、もし0x01なら0x30に書き換えする。それ以外だったらそのまま。なんて処理を妄想する。
char c; ←お解りになるだろうか?。readmemとwritememと言う c = readmem(0xB8003); 妄想関数で処理してみた。妄想関数はともかく、 if(c == 0x01){ ここがどういう処理をしているかは解るだろう。 writemem(0xB8003,0x30); } char *p; ←上記の処理をポインタを使ってやってみた。 p = 0xB8003; こんなふうに並べると、だんだん解ってこない? if(*p == 0x01){ (とにかくこれは一種のパターンとして一旦理解 *p = 0x30; しちゃうと早いと思う) }
ポインタの演算
アドレス変数(ポインタ)は演算はできるのだろうか?
結論:
アドレス変数(ポインタ)は、演算・計算はできない!
なぜ??:
数値じゃないから!!
こんな例を考える。
- 128リットル+8リットルは?
- 100枚の紙の束が6束。枚数は?
- 100万円の札束が二つ。これを5人で均等に分ける。一人いくら?
これらはみんな成り立ちます。つまり計算ができます。
では、これは??
- お前の家の住所。3倍にしたらどこの家?
- 警察に通報したいけど、110番の半分って?
- クアトロ大佐のモビルスーツ百式。二機あったら何式?
どれもおかしいですね?。つまり、数字を使っていても数値じゃないものがいっぱいあるのはわかりますね?。
アドレス変数(ポインタ)はこれに相当します。一番解りやすいのは文字通り、「これはアドレス、すなわち住所の番地なんだ」と考えるのがいいと思います。住所を足し算したり割り算したりするのはおかしいでしょう?。同じようにアドレス変数(ポインタ)を計算、演算するのはおかしいと考えれば合点がいきますね。
- それはおかしいぞ!オレはこんなのを見たことがある。コンパイルもエラーにならない。計算はできるはずだ!
char *p; p = 0x11223344; p = p + 1;
なるほど。これはエラーになりませんね。
では。掛け算や割り算はどうですか???
・・・そう、エラーになります。
「あれれ? ポインタは足し算と引き算だけができる特殊な変数なのか?」
なるほど。そういう理解もアリですが、こんな実験をしてみました。
char *c; short *s; int *i; c = 0x11111111; s = 0x22222222; i = 0x33333333; c = c + 1; s = s + 1; i = i + 1; printf("c pointer is %d\n",c); printf("s pointer is %d\n",s); printf("i pointer is %d\n",i);
さて、c、s、iはいくつになるでしょう?。普通の理屈ならどう考えても、
- c pointer is 0x111112
- s pointer is 0x222223
- i pointer is 0x333334
こうなりますよね?。ところが実際には、
- c pointer is 0x111112
- s pointer is 0x222224
- i pointer is 0x333337
こうなるんですよねぇ〜。「ポインタは足し算引き算だけできる変数」と考えちゃうと逆に理解できませんよね?。なんでこうなるのか??
これの解説はまあいろいろな考え方がありますが、私は上記通り、「ポインタは住所の番地である」という概念から説きます。
まず根本的に、ポインタへの足し算引き算なんてないのです。!
え"〜!! だって、現に
char *p; int i; p = p + 1 i = i + 1
こういう演算がされているじゃない!?
ここがミソ!!(死語)
普通の数値であるiに+しているのとポインタであるpに+しているのは、まったく意味が違うのです。
数値が相手なら、文字通り、足し算です。単に足せばよろしい。
しかし、ポインタに+するのは、何軒先のお家!という意味なのです。