プログラミング演習II

「ポインタの基礎」

ポインタ

「ポインタ」とは"pointer"と書き「pointする物(者)」すなわち「指し示す物(者)」という意味である。では、C言語での「ポインタ」が何を指し示すのかと言うと、データが格納されている場所である。C言語に限らずプログラムにおいて、変数に代入されたデータはそれが整数型であろうが、文字列であろうが、全て記憶領域(メモリ)上に格納されており、それぞれに専用の場所が割り当てられている。このデータが格納されているメモリ上の場所をアドレスと呼ぶ。

int a = 314;
char s[] = "abc";

上記コード中の整数型変数aや文字列に対して、例えば、以下の図のような形でメモリ上に配置される。
(実行環境などにより実際のアドレスの値は異なるので注意)

メモリ上のデータの図

C言語ではこのメモリ上に置かれたデータのアドレスをプログラムから扱う機能を備えており、これがポインタと呼ぶものである。その際に使用する、データのアドレスを格納するための変数をポインタ変数と呼ぶ。

ポインタ変数は以下のように、変数名の直前に"*"をつけて宣言することで使用可能となる。それぞれ、変数 pa は整数(int)型のデータが格納されているアドレスを指すポインタ変数であり、変数 ps は文字(char)型のデータが格納されているアドレスを指すポインタ変数である。

int  *pa;        /* 整数(int)型ポインタ変数 */
char *ps;        /* 文字(char)型ポインタ変数 */

Exercise 1: ポインタ変数

pvar-test.cをコンパイル・実行し、メモリ上のアドレスが表示できることを確認してください。

#include <stdio.h>

int main(void)
{
    int a = 314, b = 315;
    char s[] = "abc";

    int *pa, *pb;         /* 整数(int)型ポインタ変数 */
    char *ps;        /* 文字(char)型ポインタ変数 */

    pa = &a;         /* 変数aのアドレスをポインタ変数paへ格納 */
    pb = &b;         /* 変数bのアドレスをポインタ変数pbへ格納 */
    ps = &s[1];      /* 文字列の2番目のアドレスをポインタ変数psへ格納 */

    printf("pa=%p\n", pa);
    printf("pb=%p\n", pb);
    printf("ps=%p\n", ps);

    return 0;
}

実行すると、以下のようにポインタ変数のそれぞれの値が表示されます。(実行環境によって値は異なる)

pa=0x7fffffffdb50
pb=0x7fffffffdb54
ps=0x7fffffffdb75

ポインタ演算子

ポインタ演算子には、変数が格納されているアドレスを計算するアドレス演算子 &と、ポインタ変数が指し示すアドレスに格納されている値を参照する間接演算子 *とがある。

ポインタ演算子を使うと、以下の図のように、ポインタが指すアドレスの値にアクセスできる。(アドレスの値は実行環境により異なるので注意)
変数aとポインタ変数paのどちらもメモリ上に存在し、ポインタ変数はpa = &aのため、変数aのアドレス&aを値として保持している。最初、変数aは314という値であるが、*pa = 141とすることで、ポインタ変数paを経由してその値を141に書き換えられている。

ポインタ変数と変数のメモリ上での関係

下向き矢印

ポインタ変数経由で変数の値を変更

このようにポインタを介することで変数の値を間接的に読み出したり書き換えたりすることが可能である。

Exercise 2: ポインタ演算子

ptrop-test.cをコンパイル・実行し、前述した図の動作を確認してください。

#include <stdio.h>

int main(void)
{
    int a = 314;
    int *pa;        /* 整数(int)型のポインタ変数 pa */

    pa = &a;        /* アドレス演算子により変数aのアドレス計算 */

    printf("before write (a=%d)\n", a);

    *pa = 141;      /* 間接演算子によりポインタ経由で変数aの中身を書き換え */

    printf("after write (a=%d)\n", a);

    return 0;
}

実行結果は以下のようになり、ポインタ変数を経由することで変数の内容を書き換えていることが分かります。

before write (a=314)
after write (a=141)

複雑なポインタ(ポインタのポインタ)

ポインタ変数も「変数の一種」であるため、メモリ上にデータが格納されている。
そこで、ポインタ変数のアドレスを別のポインタ変数に記憶させるということも可能である。

以下のサンプルコードptrptrop-test.cはポインタのポインタを使った例である。

#include <stdio.h>

int
main(void)
{
    int a = 314;
    int *pa;        /* 整数(int)型のポインタ変数 pa */
    int **ppa;      /* 整数(int)型のポインタのポインタ変数 ppa */

    pa = &a;        /* アドレス演算子により変数aのアドレス計算 */
    ppa = &pa;      /* アドレス演算子によりポインタ変数paのアドレス計算 */

    printf("before write (a=%d)\n", a);

    **ppa = 141;    /* 間接演算子を2回使うことでポインタのポインタ経由で変数aの中身を書き換え */

    printf("after write (a=%d)¥n", a);

    return 0;
}

実行結果は前述のプログラムと同じであるが、このプログラムではポインタのポインタを経由して変数の中身を書き換えている。

このコードの場合の変数間の関係は以下の図のようになっている。(アドレスの値は実行環境により異なるので注意)
変数a、ポインタ変数paおよび ppaはいずれもメモリ上に存在し、ポインタ変数paは変数aのアドレスを、ポインタ変数ppaはポインタ変数paのアドレスを値として保持している。最初、変数aは314という値であるが、ポインタ変数ppaの指すアドレスに格納されているアドレスの値を141に書き換えられている。

ポインタのポインタの例(その1)

下向き矢印

ポインタのポインタの例(その2)