さて、前回までの記事でC言語にポインタが登場する理由が分かったので、あとはポインタの使い方をマスターすれば完璧だ。
これで貴方もCポインターマスターになれる。
では、本題に入ろう。
C言語入門[3/5] 初めてポインタを使ってみる
人生初めてのポインタを使う
例えば、C言語でポインタを使う場合には、
int a = 100; int *ptr = &a;
こんなふうに書けば
ptrはint型変数aのアドレスを保持するポインタ型の変数となる。
ちなみに、
int* ptr = &a; // ① int *ptr = &a; // ② int*ptr = &a; int * ptr = &a; int * ptr = &a;
など何でも良いのだが、あまりヘンテコな事をすると混乱の元なので普通は①か②のどちらかが使われるだろう。
①なら int* はint型のポインタを保持する型と言う感じか。その型のptrと言う変数。
②なら *ptr というint型の変数 と言う感じか。なのでptrは結局int型のポインタ変数になる。
ちなみにK&Rの教科書では②の記述で書かれているので、ワテも昔から②の記述を使ってきたのだが、最近では、もうどっちもで気にしない。
で、ポインタを使ったサンプルプログラムを実行してみると、
#include <stdio.h> int main(int argc, char **argv) { int a = 100; int*ptr = &a; printf("%d\n",a); printf("0x%08x\n", ptr); getchar(); }
実行結果
100 0x0038fc24
と出力される。
人生初のポインタプログラムを実行した事になる。
100は整数型変数aの中身であり、0x0038fc24はアドレスの値であり、100がメモリ上のこのアドレスに保管されている事を意味する。
この点を良く理解する事が重要だ。
あなたがプログラムの中で変数aの値を使いたい場合、そのaと言う文字を書けばあとは何も気にしなくてもaの値が利用できる。その値は、直前にaにセットされている値だ。今の例では100。
でもそのaの実体はメモリ上のどこかのアドレスに保管して管理する必要があるので、プログラムを実行した瞬間にまず最初に、aと言う変数の値を保持する為にメモリ上の0x0038fc24番地から4バイト分の部屋を確保しているのだ(4バイトの理由はint型は4バイトなので)。
C言語では、そういう裏方さん(OSと実行プログラム)がやっている仕事の内容も全部オープンになっていて、プログラム自身で自由にそういう情報が見られると言う事なのだ。
64bitアドレス
ワテの場合Windows7の64ビット版の上でVisual Studioを使っているが、ビルド(=コンパイル、リンク)して生成される実行プログラムは32bit版、あるいは64bitのどちらでも選択して生成する事が可能だ。
デフォルトでは32bitアプリが生成されるので上記のように、ポインタ情報を取得すると、
0x0038fc24
のように16進数で8桁のアドレスが得られる。
16進数の1桁は0~Fまでだから4bitなので、8桁の場合なら 8×4 = 32bit となる。
一方、Visual Studioの設定で、
Win32(=32bitアプリ)を x64に変更すれば64bitアプリが生成出来る。とても簡単だ。
それを実行して、デバッガで停止して変数のアドレスを見てみると、
0x000000000026f7e8
などとなっている。16進数で16桁だ。なので16桁 x 4 bit = 64bitのアドレスを使う正真正銘の64bitアプリである事が分る。
Windowsの場合、タスクマネージャーの画面を出すと実行中のプロセスを確認出来るが、下図のようにイメージ名のところに *32 と付いているのが32bitアプリだ。
なので何も付いていないプロセスは64bitアプリである事が分る。
printf(), getchar()について
printf("%d\n",a);
は、変数aの内容を10進数(decimal)表示すると言う意味。
printf("0x%08x\n", ptr);
は、変数ptrの内容を16進(hex)表示すると言う意味。
%x 16進表示 %8x 16進表示8桁 %08x 16進表示8桁で0埋め
となる。
printf関数の書式設定はこれら以外に沢山あるので、よく覚えておくと何かと役立つだろう。
getchar();
は、Visual Studioで実行した場合にコンソール画面に実行結果が表示されて一瞬で消えてしまうのを防止する為に、ここでキーボードからの入力待ちにする小細工だ。
そうするとコンソール画面が一瞬で消える事はなく、何かのキーを押すまでは表示が停止している。
&aとは何か?
さて、ポインタ初心者の人にとって
int*ptr = &a;
この部分が最初の難関だと思うが、難しくはない。
&a は整数型変数aが保管されているメモリ上のアドレスを意味する。
&はアドレスを取り出すと言う意味の記号だ。
それを int* 型の変数である ptr に代入しているだけだ。
int* ptr は上で説明したように、ptr変数を整数型変数のアドレスを保管する用途に使うという宣言だ。
上記の例では変数宣言と同時に値の代入も行っているが、勿論、
int *ptr; // ポインタ変数宣言 ptr = &a; // ポインタ変数にaのアドレスを代入
のように宣言と代入を分離しても良い。
初心者の人が間違い易いのは、2行目の記述だ。これを、
int *ptr; *ptr = &a; // これは間違い
とすると文法的には間違いではないが、異なる意味になる。
つまり*ptrとした場合には、「ptrが指すアドレスの中身」という意味になるが、今の例ではptrはまだ初期化されていないので値は未定だ。その未定なアドレスの中身を取得しようとしているので*ptrの部分でコンパイラがエラーを表示するだろう。この辺りの説明は下のほうで再び出て来る。
さて、&記号は文法的には&aのように変数の前に付けるとその変数のアドレスを取り出す演算子という意味である。
正式にはアドレス演算子と言うそうだが、ワテの場合には、このアンド記号の意味を
「変数aのアンドレス」
を取り出す記号と覚えている。
ワテ流の覚え方だ。
で、その&aで取り出した変数aのアドレスを、アドレスを保持する変数ptrに代入するので、
ptr = &a;
が正解となる。ptrの頭に*を付けてはいけない。
ちなみに、「変数 アンドレス」でGoogle検索してみたら幾つかのC言語解説サイトでワテ流の覚え方と全く同じ説明をしているサイトが見つかった。ワテと気が合うかも知れない。
さらにもう一つ覚えよう。
それは、
int *ptr = &a;
で定義したポインタ変数ptr自身も変数であるから、それはメモリ上のどこかのアドレスに保管されていてその中に値(=aのアドレス)が保管されている。
なので、&ptr とすればそのptrが保管されているアドレスが取得出来る。この&ptr を何らかの変数に保管したい場合には、
int **pptr = &ptr;
とすればpptrという変数にptrのアドレスを保管出来る。
ややこしいぞ。
でも、考え方はこうかな。
int a のアドレスを保持するのが int *ptr
int *ptr のアドレスを保持するのが int **pptr
と言う感じ。
ptrもpptrもアドレスを保持する変数には変わりないので、32bitアプリならそれは4byteの大きさであり、メモリ上のどこかに変数として管理されている点は、変数aもptrもpptrも何ら変わりない。
その内容が、
整数aなのか、
整数型変数aのアドレスなのか、
整数型変数aのアドレスを保持する変数ptrのアドレスなのか
の違いだけだ。
なお、上に書いたように32bitアプリならptrもpptrも整数aと同じ4バイトの大きさになるので、
int **pptr = &ptr; // ① 正しい
を
int *pptr = &ptr; // ② こう言う書き方や int pptr = &ptr; // ③ こう言う書き方をしてもエラーはしない
と書いても、pptrには4バイトの大きさの領域が確保されるので、Visual Studioのコンパイラではエラーはしなかった。
う~ん、まあでも*ptrのアドレスを保管するなら、①のように**pptrと書くのが正統的だと思う。
さて、これであなたも&の意味が理解出来たので、これからは何か変数があればその頭に&を付ければその変数が格納されているメモリ上のアドレスが取得出来るようになった。
*ptrの意味は?
ここまでで
int *ptr; ptr = &a;
の意味が理解出来た。理解できていない人はワテの説明が悪いのかも知れないが。
分かりにくい点がありましたら、コメント欄でお知らせください。
さて、ptrにはアドレスaを代入したので、ptrの中身は変数aのアドレス(0x0038fc24)だ。
では、ptrというポインタ変数が与えられた時に、そのポインタが指すアドレス(0x0038fc24)にあるメモリの中身(つまり変数aの中身)を知りたい時には
*ptr
という表現で変数aの中身が取り出せる。
なので、
printf("%d\n", *ptr);
を実行しても、同じく100が表示される。
つまりptrはアドレスを保持する変数なので、*印を頭に付けるとアドレスの中身が取り出せるのだ。
ワテ流の覚え方では、*ptrの意味は、
「ptrの中身が*い」
「ptrの中身が★い」
「ptrの中身が星い」
「ptrの中身が欲しい」
となる。
でもまだ良く分からん。
と言う人も多いだろう。
分かりにくい理由の一つは、ポインタの宣言
int *ptr; // ポインタ変数宣言
でも *ptr の形式が登場するが、そのポインタを使う場面でも同じく
printf("%d\n", *ptr);
のように *ptr の形式が登場する点だろう。
もしその辺りで混乱している人は、こう考えれば分かり易い。
typedef int* intptr; int main(int argc, char *argv[], char *envp[]) { int a = 100; int * ptr = &a; intptr ptr2 = &a; printf("%d\n", a); printf("0x%08x\n", ptr); printf("0x%08x\n", ptr2); }
つまりC言語にあるtypedefという命令を使うと、文字通り型(type)を定義(define)出来るのだが、それを使って
int*
というやつを
intptr
という自分用の型として定義しておく。
これを使って
intptr ptr2 = &a;
というふうに ptr2 変数を定義すれば良い。
要するに int* というのを int + * と言う風に分離して考えるのではなくて、
int型のポインタと言う一つの型としてintprt型だと思えば良い(というより、実際にそうなのだが)。
で、ポインタの中身が欲しい時には★を付けて、
printf("%d\n", *ptr2);
と書けば良いのだ。
ptr2の中身が欲しいとなる。
その結果
100
が得られる。
それでもまだ良く分からん。
と言う人も多いだろう。
多分、その理由は、多くの皆さんは、
「そもそもアドレスを取得して何がいいの?」
「ポインタって何に使うの?」
という疑問がまだ解消していない人が多いと思う。
確かにそうだ。
使う目的も分からないのに、変数のアドレスを取得する方法を覚えてもピンと来ないのは当然だ。
なので、ポインタをどういう時に使うのかについてワテ流に解説したいと思う。
続く。
子供には負けたくないな。
コメント