ログイン
kid's world

ポインタ変数

ポインタ変数

変数の確保したメモリの先頭アドレスを,アドレス演算子で知ることができることを学んだが,これはいったいどんな目的に使われるのだろうか。実はアドレスがわかることによって,いろいろとおもしろいことができるようになる。

次回以降,アドレスを使ったいろいろな例について解説するので,今回はその基礎となる「ポインタ」について解説しよう。

ポインタ変数の宣言

変数の確保したメモリのアドレスはよく使われるので,それを記憶する変数がある。それをポインタ変数と呼んでいる。ではさっそくポインタ変数を使ってみよう。

ポインタ変数は,

型 *変数名;

のように宣言する。たとえば,

int *p;

と書く。しかし,この宣言で「*p」という変数を宣言したのではないということに注意したい。このポインタ変数の名前は,「p」である。したがって,たとえば変数iのアドレスを記憶したければ,

p = &i;

と,代入演算子を使って記憶することができるわけである。

この代入によって「p」は変数iが確保したメモリの先頭アドレスを記憶した。言い方を変えれば「変数pは変数iを指し示している」わけである。指し示すことを英語で「ポイント」というが,ポインタ変数の名前の由来はそこにある。

そこで気をつけなければならないのは,ポインタ変数はアドレスだけではなく,「型」もわかっているということである。もう一度ポインタ変数の宣言文を見てみよう。

型 *変数名;

先ほどは何げなく

int *p;

と書いたが,これは「int型変数を指し示すポインタ変数p」を宣言したということになる。だから,もしfloat型変数のアドレスを指し示すポインタ変数qを宣言したければ,

float *q;

と宣言するのである。

間接演算子

さて,ポインタ変数を使って,何ができるのだろうか。ここで1つ,おもしろい演算子を紹介しよう。それは,間接演算子である。この演算子は,右側にポインタをとる演算子で,記号は「*」である。

ちょっと混乱しそうなので,整理しよう。ポインタ宣言のときにも「*」を使った。しかし,これとは違う。ポインタを宣言するときに使った「*」は,演算子ではない。

また,掛け算を行う算術演算子として「*」を使った。しかし今話題にしている間接演算子はこれとは違う。掛け算を行う算術演算子「*」は,右と左に「10」という定数や「i」などの変数をとる演算子だ。

定数や変数をまとめて「式」と言い換えると,

式*式

と書くことができる。このような式を2つとる演算子を「二項演算子」と呼んでいる。足し算や引き算もそうである。

それに対して,今回説明する間接演算子は右側にだけ式(ポインタ変数)をとる「単項演算子」である。

*ポインタ変数

これらの3つの違いをしっかりと理解していただきたい。簡単にまとめると,

(1)変数宣言の「*」はポインタ変数の宣言

(2)二項演算子の「*」は乗算演算子

(3)単項演算子の「*」は間接演算子

である。

では,間接演算子の具体例を示そう。まず,int型の変数iと,それを指し示すためのポインタ変数pを宣言する。変数iの値は,100で初期化しておく。

int i=100;
int *p;

次に,変数iのアドレスを,ポインタ変数pに代入する。

p = &i;

そして,ここで間接演算子を使う。つまり「*p」である。さて,この「*p」は何を意味するのだろうか。

pは「int型を指し示すポインタ」として宣言された。そのポインタに対して間接演算子「*」を使うと,int型の値になる。では,その値とは具体的に何か?

答えは,pの指し示すアドレス以降に記憶されているint型の値,すなわち,変数iの値となる。つまり,間接演算子はポインタ変数の指すアドレスに書き込まれたデータを,ポインタ変数の型のデータとして解釈する演算子なのだ。よって,

printf("%d\n",*p);

と書くと,間接的に変数iの値が表示される。

ちょっと頭の整理を

初めてのことだらけで頭が混乱していると思う。そこで,ポインタ変数について説明してきた内容をすべて含む,簡単なプログラムをList 2に示した。1行ずつ,じっくりと解読していただきたい。

List 2:説明した内容をすべて含むプログラム

#include <stdio.h>

main()
{
  int i, *p;

  i=100;
  p=&i;
  printf("変数iのサイズは%d、変数pのサイズは%d\n",sizeof(i),sizeof(p));

  printf("変数iの値は%d、変数iのアドレスは%p\n",i,&i);
  printf("変数pの値(アドレス)は%p、変数pのアドレスは%p\n",p,&p);
  printf("*pは、%d\n",*p);

  i=200;
  printf("変数iに200を代入しました。\n");

  printf("変数iの値は%d、変数iのアドレスは%p\n",i,&i);
  printf("変数pの値(アドレス)は%p、変数pのアドレスは%p\n",p,&p);
  printf("*pは、%d\n",*p);
}

筆者のPCのLSI C-86でコンパイルして実行すると,Fig. 3のような実行結果となった。この実行結果から,筆者のPCのメモリは,Fig. 4のように使われていたことがわかる。

Fig. 3: List 2の実行結果

変数iのサイズは2、変数pのサイズは2
変数iの値は100、変数iのアドレスは1038
変数pの値(アドレス)は1038、変数pのアドレスは1036
*pは、100
変数iに200を代入しました。
変数iの値は200、変数iのアドレスは1038
変数pの値(アドレス)は1038、変数pのアドレスは1036
*pは、200

Fig. 4: 変数iとpの位置関係

/c/pointer-fig4.png

つまり,1038と1039番地のメモリを使ってint型の変数iは100(のちに200が代入される)という値を記憶し,1036と1037番地のメモリを使って,int型を指し示すポインタ変数pは,1038(16進数)という値を記憶していたというわけである。

これで少しポインタ変数のことがわかっただろうか。乱暴にいってしまえば,ポインタ変数というものは,ある変数のアドレスと型を覚えているものにすぎない,ただそれだけの変数なのである。

ポインタ変数でメモリを書き換える

さて,次がおもしろいところである。ポインタ変数によって,ほかの変数の内容を盗み見ることができたわけだが,さらに「書き換え」てしまうこともできる。書き換えられる変数の側からするとなんとも迷惑な話ではあるが,プログラマがそれをわかったうえで賢く使えば,非常に効率的なプログラムを作ることができるのだ。それでは,書き換えてしまう実例を示そう。

List 3: メモリを書き換える例

#include <stdio.h>

main()
{
  int i,*p;
  
  p=&i;
  i=100;
  
  printf("%d\n",i);
  *p=200;
  printf("%d\n",i);
}

List 3には,

printf("%d\n",i);

という変数iの内容を表示する文が2か所出てきている。その間に挟まれているのは,

*p=200;

という,なんとも興味深い文である。実行結果を見てみよう。なんと変数iは,最初に「100」という数値を代入されたあとは指1本触れられていないのにもかかわらず,最後には「200」という数値に書き換わってしまっている。

このカラクリの中核は「*p=200;」という1文にある。「*p」は,ポインタ変数pの指し示す先のメモリの内容を表しているのだ。だから,これに対して代入をすると,そのメモリの内容が書き換わる。

ポインタ変数pの指し示す先は変数iによって確保されたメモリ領域である。したがって,変数iの内容が間接的に変わってしまったのだ。

このように,ポインタ変数と間接演算子を組み合わせれば,ほかの変数の内容をメモリに直接アクセスして読み込んだり,書き込んだりできるのである。


決してやってはならないこと」へ進む

広告


©Toshio Koide 1996-2007.

目次

リンクについて

リンクは御自由にどうぞ。

メール

mail.gif

広告