Cによるプログラミング入門7
アドレスとポインタ

 今回は、いろいろ「難しい」と言われているポインタの話です。細かいことを言い出すとキリがないのですが、「どんなものか」を理解することは難しくありません。まあ、気楽にいきましょう。
 ただ、おもしろいかというと、人によっては退屈かもしれません。趣味でこのページを読んでいる方は、今回は、斜め読みでもいいかもしれません。(私の学生はそういうわけにはいきません。)


 一般に、変数は

int x;

などと定義するのでした。プログラム中にこのようなコードがあると、メモリ内にスペースが確保され、そこに(一時的にですが)xという名前が付けられるわけです。
 メモリは、たくさんの箱が並んだようなものをイメージするとよいようです。

Fig.1 メモリと変数のイメージ
(ペイントで書くの大変でした)

 Fi.g1では、四角の箱がメモリの「単位」で、1バイトの容量を表します。1バイトとは、0か1を(普通)8個並べられる「スペース」のことです。これを8ビットなんて言うんでしたね。
 そして、これらの箱には番号が振られていると考えます。それをアドレスというのです。(ただし、Fig.1の番号は、説明用に適当に書いたものです。また、一般的には、表記に16進数が使われます。)
 たとえば、Fig.1の赤で書いた場所に、int型変数xの場所を確保しているわけです。int型変数がどれだけの場所(箱)を取るかは、環境次第ですが、現在の普通の環境では4バイトです。

 さて、変数が複数個の箱を使う場合、「変数が使う箱の先頭の箱の番号」を「変数のアドレス」ということができます。Fig.1の例で言うと、xのアドレスは、先頭の箱の番号、つまり10000002ということになりますね。
 一般的なプログラムを書いているときには、変数xのアドレスをプログラマが決めることはありません。自動的に、割り振られるのです。しかし、場合によって、(自動的に割り振られたにせよ)変数のアドレスが必要になることがあります。
 このアドレスを得るには、「&x」とすればよいことになっています。つまり、&xと書くと、それがxのアドレスを意味するのです。
 なぜ、アドレスなどが必要になるのでしょうか。それは、Cという言語の性質から来るものなのですが、はじめは、「アドレスを必要とする関数があるから」と思ってもよいでしょう。たとえば、scanfです。scanfを使うには、「scanf("%d", &x);」のように、アドレスを与える必要があるのです(「&x」はアドレスだったんです)。scanfは、「&x」によって、xの「位置」(整数データを格納する場所)を知り、そこにユーザが入力した値を置くわけです。とりあえずは、「このようにアドレスが必要になる」と思っておいてください。


 普通のプログラムを書いている場合、アドレスの具体的な値が必要になることは、あまりありません。アドレスは、まず、「&x」のような形で現れるのです。しかし、具体的な値を必要としない場合でも、その値をどこかに保持しておく必要が出てくることがあります。
 そのような場合に使えるのがポインタというものです。ポインタとは、アドレスを保持する変数のことです。たとえば、int型変数のアドレスを保持する変数、つまり「int型のポインタ」は

int* p;

あるいは、

int *p;

のように定義できます。*は、どちらに付けてもかまいません。これで、pがポインタになるのです。
 ポインタを定義しておくと、

p = &x;

のように、xのアドレスをpに代入することができるわけです。
 逆に、「pが保持しているアドレスにある変数」は、「*p」のように表すことになっています。つまり、

*p = 5;

とすると、「*p」は「pが保持しているアドレスにある変数」、つまりxを意味し、xに5を代入したことになるのです。(ここで「int *p;」は「pという変数の定義」であるのに対し、「*p = 5;」では、「pを使っている」ことに注意してください。似ていますが、違うことをしているのです。)
 ここまでをサンプルで確かめてみましょう。

/* apsample.c */
#include <stdio.h>

int main(void)
{
    int x = 10;
    int* p;
    p = &x;
    *p = 5;
    printf("xの中身は%dです。\n", x);
    return 0;
}

Fig.2 apsample.exeの実行画面
(xの値は10ではなく5になっている)

 もちろん、意味のあるプログラムではありません。これは、アドレスとポインタを見るための例にすぎないのです。
 なお、上のような状況で、「pはxを指す」などという言い方もします。それは、pが「xの位置(アドレス)」を保持し、どこにxがあるかを記録しているからです。「指す」は英語のpointの日本語訳です。ポインタという言葉が使われるのは、このためです。
(実は、英語では、point(指す)と同様の意味のrefer(参照する)も使われます。これを訳すと「pはxを参照する」となります。Cで「参照する」とあれば、「指す」と同じ意味だと思ってください。ただし、C++の場合、「参照」はまた別の意味に使われので、ちょっと、ややこしいです。とりあえず、「pはxを指す」でいきましょう。)

 上記の「&」はアドレス演算子、「*」は間接参照演算子(または、逆参照演算子)などとよばれています

 以下に、いくつか、注意を書いておきましょう。

●「int* p;」と「int *p;」のように、*は、どちらに付けてもかまいません。ただ、たとえば、ポインタを2つ定義したい場合、

int* p1, p2;

と書いてしまいそうですが、これでは、p1だけがポインタになってしまうので注意が必要です。p2はただのint型変数になるのです。(そのように決められているのです。)p2もポインタにしたければ、

int *p1, *p2;

などと書く必要があります。(「int* p1, *p2;」でもよいのですが、これでは、ちょっと不統一ですね。)

●一般に、変数の種類を型というのでした。たとえば、「int x;」と書くと、「xはint型変数」「xの型はint」などと言います。「int* p;」(または「int *p;」)と書いたときの変数pの型は、「int*」ということになります。

●ところで、上で「ポインタはアドレスを格納する変数」(p)と書きましたが、状況により、少しずつ違う意味にも使われます。たとえば、「アドレスを格納する変数の型」(int*)をポインタということもあります。変数のつもりか型のつもりかをはっきりさせるために、単にポインタではなく、ポインタ変数(または、ポインタ型の変数)、ポインタ型と言って両者を区別することもあります。また、ポインタ(変数)に格納される値を、ポインタ(アドレスですね)と言ってしまうこともあります。

●こう書くと、混乱してしまうかもしれませんが、結局、今回のサンプルの動作が理解できれば、「(基本は)理解した」ことになると思います。


 実は、前回紹介した配列は、ポインタと深く結びついています。
 たとえば、

int a[10];

と書くと、10個のint型変数、

a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9]

が定義されたことになるのでした。
 これらの配列要素は、メモリ上に順番に並べられることになっているのです(Fig.3参照)。その先頭の要素a[0]のアドレスは「&a[0]」で与えられるわけですが、実は、配列の名前「a」も先頭の要素のアドレスを表すようにも決められているのです。つまり、aは配列の名前なのですが、それは「配列の先頭を指すポインタ」でもあるわけです。(ただし、今の場合、値を代入できる変数ではありません。)

 ここで、scanfで文字列を受け取るときのことを思い出してみましょう。それは、たとえば、

char str[100];
scanf("%s", str);

のようにするのでした。2行目のscanfのstrは、配列の名前ですが、これは「文字配列の先頭」を示しているのです。そう考えると、整数を受け取るときの「scanf("%d", &x);」などと、矛盾していないのです。


 ポインタにはおもしろい性質があります。たとえば、intの配列

int a[10];

に対し、

int *p;
p = a;

としたとします。こうすると、pがaの配列の先頭のアドレス(=a[0]のアドレス)を保持することになりますね。(aがそういう値を持っていて、それをポインタのpに代入するからです。)
 ここで、「p + 1」とすると、どうなるでしょう。aの値を代入したpには、「a[0]が占有する箱の先頭の箱の番号」があるはずです。a[0]はint型なので、メモリの箱を4つ占有していると考えると(Fig.1参照)、「p + 1」は「a[0]が占有する4つの箱の2番目の箱の番号」ということになりそうです。しかし、違うのです。
 pは、自分が「int型変数のアドレス」を保持していることを知っています。そのため、「p + 1」は、「次の要素、つまりa[1]が占有する箱の先頭の箱の番号」になるのです。つまり、「p + 1」は「a[1]のアドレス」なるのです。同様に、「p + 2」は「a[2]のアドレス」になります。

Fig.3 配列とポインタ
(pが10000002のとき、p + 1は10000006)

 これらに*を適用すると、もとの変数を意味するのでした。つまり、

*p    は a[0]と同じ
*(p + 1) は a[1]と同じ
*(p + 2) は a[2]と同じ

*(p + n) は a[n]と同じ

ということになるのです。
 初心者にはわかりにくい形ですが、このような事実を使うプログラムもあるので、ここで覚えることにしてください。
 役には立ちませんが、簡単なサンプルは次のようになります。

/* apsample2.c */
#include <stdio.h>

int main(void)
{
    int i;
    int a[10];
    int* p;
    p = a; /* &aではない。&a[0]ならOK */
    for(i = 0; i < 10; i++)
        *(p + i) = 100 + i;   /* 「a[i] = 100 + i;」と同じ。 */

    printf("配列aの中身は...\n");
    for(i = 0; i < 10; i++)
        printf("%d ", a[i]);
    printf("\nです。\n");
    return 0;
}

Fig.4 apsample2.exeの実行画面

 なお、apsample2.cでは、「処理内容が1行しかないfor文では、中カッコを書かなくてもよい」というルールにしたがって、for文の中カッコをはずしていることに注意してください。


 今回は、ちょっとヘビーですね。ヘビーついでに、もう1ついってみます。これで最後です。

 前に、文字列は「""」で囲むという話をしました。たとえば、

char str[] = "hello";

と書くと、配列strが用意され、その要素が「h、e、l、l、o、\0」で初期化されるのでした。「""」で囲まれた文字列を文字列リテラルなどといいます。strはcharの配列であって、その要素を文字列リテラル「"hello"」を使って初期化したことになるのです。
 ところで、「"hello"」と書くと、それだけで、メモリのどこかにこの文字列の場所が用意され、その場所に、順に、「h、e、l、l、o、\0」が置かれることになっています。そして、「"hello"」そのものは、その「どこか」の場所の先頭のアドレスを表すことになっているのです。
 これは、実体としては、文字の配列の先頭ですから、その場所をcharのポインタに格納することができてしまいます。つまり、

char *p = "hello";

などと書くこともできるのです。
 ここで「char *p」は、「pがcharのポインタ」であることを意味します。そのため、文字配列の先頭のアドレスを格納できるのです。int配列の先頭のアドレス(=配列の名前)は、int*型の変数に格納できましたね。それと同じです。
 このようにしても、pが、文字列を表していることに違いはありません。そのため、

printf("pの指す文字列は%sです。\n", p);

とすると、

pの指す文字列はhelloです。

と出力されることになるのです。

/* apsample3.c */
#include <stdio.h>

int main(void)
{
    char *p = "hello";
    printf("pの指す文字列は%sです。\n", p);
    return 0;
}

Fig.5 apsample3.exeの実行画面

 ただし、strとpは同じ「"hello"」という表現から構成し、同じ「hello」という文字列を表しているわけですが、異なるものであるということも理解しておかなければなりません。
 pはどこかに確保された文字列を指すポインタであるの対し、strはプログラマが意識して確保した配列(その先頭の要素を指し示すもの)なのです。たとえば、配列strの中身はあとで自由に変えてよいのに対し、pの指す場所にあるデータを

*p = 'H';

のようにプログラマが変更することは許されていません。やってみるとうまくいくかもしれませんが、それはたまたまであって、いつもうまくいくわけではないのです。

課題7


C入門目次

前のページ  後のページ