ポインタ

ポインタとは

プログラム中で変数を使用すると、その変数の値はメモリ上に一時的に保存されます。
変数だけでなく、関数を定義すればその関数の実装もメモリ上に保存され、必要に応じて呼び出されます。

こういったメモリ上に展開されたデータにアクセスするには、そのデータがメモリ上のどこにあるのかを知っておかなければなりません。
通常、プログラマは変数がメモリ上のどこに存在するかを意識することなく変数を使用することができます。
これはプログラマが意識しないところでプログラミング言語が「上手いこと」やってくれているのです。

しかし、あえて明示的にメモリ上の場所を意識してプログラミングをすることもできます。
それがポインタという概念です。

まずは以下のサンプルプログラムを見てみましょう。

#include <stdio.h>

int main()
{
    int kazu = 10;
    int *pointer = &kazu;

    printf("%d\n", pointer);
    printf("%08x", pointer);
    getchar();
}
7862884
0077fa64

実行結果に表示された数値(または文字列)が変数kazuのメモリ上の位置を表す情報です。
表示形式の違いについては後述します。
※実際に表示される値はプログラムを実行するたびに変わります。

5行目はなんの変哲もないint型変数kazuの使用宣言です。
6行目では変数kazuに何やら奇妙な記号を付け、それをこれまた奇妙な記号を付けられたint型変数pointerに代入しています。

何らかの変数の宣言をすると、その変数の値を保存する領域がメモリ上に自動的に作られます。
この「メモリ上の位置」をアドレスといいます。

6行目の右辺(「=」の右側)では、変数kazuの名前の前に「&」記号がつけられています。
この記号をアドレス演算子といいます。
通常の変数にアドレス演算子を付けると、その変数が保存されているアドレスを得ることができます。

6行目の左辺では、変数名の前に「*」記号が付けられています。
この記号を間接演算子といいます。
変数名に間接演算子を付けて宣言した変数をポインタ変数といいます。
ポインタ変数は通常の変数とは違い、他の変数や関数などのアドレスを保存します。

つまり6行目は、変数kazuのメモリ上の位置をアドレス演算子によって取り出し、ポインタ変数pointerに保存していることになります。

間接演算子は乗算演算子(掛け算)と同じ記号です。
演算子は使用する場所によって意味が異なるものがいくつかあります。

ポインタのイメージはこのようになります。
ポインタのイメージ
※この画像中のアドレスは適当です。
あくまでイメージ図です。

アドレスの表示形式

8、9行目のprintf関数は、ポインタ変数pointerが持つアドレスの値を表示しています。
アドレスというのはただの整数値なのですが、桁が多いためそのままではやや見辛いので16進数で表記することが多いです。
9行目では「%08x」とすることで16進数表示にしています。
(ポインタの表示は「"%p"」でも良いです)

10進数は値を0~9の10個の数値で表します。
16進数は9までは0~9の数値を使用しますが、文字が足りないので10~15はA~Fのアルファベットで表します。
(大文字でも小文字でも意味は変わりません)

16進数の「0F」は10進数では「15」となります。
16進数の「10」は10進数では「16」となります。

ポインタ変数を通して値を書き換える

「ポインタ変数に、他の変数のアドレスを保存する」という説明では、それに何の意味があるのかいまいちピンと来ないかもしれません。

ポインタ変数pointerは、変数kazuのメモリ上の位置を保存しています。
メモリ上の位置が分かるということは、その変数の値にアクセスすることができるということです。

#include <stdio.h>

int main()
{
    int kazu1 = 10;
    int *pointer = &kazu1;

    int kazu2 = *pointer;
    *pointer = 20;

    printf("kazu1: %d\n", kazu1);
    printf("kazu2: %d\n", kazu2);
    getchar();
}
kazu1: 20
kazu2: 10

変数kazu1は初期化してから値を操作していないのに、保存されている値が変わっていることに注目してください。

5、6行目は最初のサンプルコードと同じです。

8行目では、右辺(「=」の右側)のポインタ変数pointerに間接演算子(*)を付け、それを新しく宣言した普通のint型変数kazu2に代入しています。

ポインタ変数に間接演算子を付けると、そのポインタ変数に保存されているアドレスの「値」にアクセスできます。
つまり、この状態のポインタ変数pointerは、変数kazu1と同じ意味を持ちます。
ポインタ変数pointerには変数kazu1のメモリアドレスが保存されていますから、変数kazu2には変数kazu1の値である「10」が代入されます。

もしここで「*」を付けないと、変数kazu2には変数kazu1のアドレス(最初のサンプルコードで出現した「7862884」などという訳の分からない値)を代入することになります。
(型が違うためそのまま代入できずエラーになります)

9行目ではポインタ変数pointerに「*」を付けたものに「20」を代入しています。
ポインタ変数に間接演算子を付けるとアドレス先の値にアクセスできるので、これはアドレス先の値、つまり変数kazu1に「20」を代入しているのと同じことになります。
11行目で変数kazu1の値を表示してみると「20」に書き換えられていることがわかります。

ポインタのイメージ2

ポインタとは「指し示すもの」

ポインタなど使わずとも、普通に変数を使えば値の読み書きはできるのだから、ポインタに意味がないように思えるかもしれません。
ポインタが有効に使える場合はおいおい説明しますが、まずはポインタ変数と通常の変数との違いを明確にしておきましょう。

#include <stdio.h>

int main()
{
    int kazu1 = 10;
    int *pointer = &kazu1;

    printf("*pointer: %d\n", *pointer); //10

    int kazu2 = *pointer;
    *pointer = 20;
    printf("*pointer: %d\n", *pointer); //20

    pointer = &kazu2;
    printf("*pointer: %d\n", *pointer); //10

    getchar();
}
*pointer: 10
*pointer: 20
*pointer: 10

8行目と12行目で表示される値は、ポインタの基本事項が理解できれば何が表示されるかは分かるでしょう。
8行目は変数kazu1の値を参照しているのと同じなので「10」。
11行目でポインタ変数を通して変数kazu1の値を書き換えているので、12行目では「20」が表示されます。

14行目では何やら代入操作が行われています。
その結果、15行目では表示が「10」に変わります。

14行目の左辺、ポインタ変数pointerには間接演算子がありません。
つまり、この場合はアドレスを扱います。
そして右辺では変数kazu2のアドレスを取り出しています。

つまりこれは、ポインタ変数pointerに保存しているアドレス情報を、変数kazu2のアドレスで置き換える処理をしています。
変数kazu2には10行目で「10」が代入されていますので、結果として15行目では「10」が表示されたのです。

ポインタのアドレスの変更

ポインタ変数も変数には違いありませんから、値(アドレス)は変更できないのではなく、別のアドレスを代入することで指し示す先が変わるのです。

ポインタ変数の注意点

間接演算子の有無

ポインタ変数に対する間接演算子の有無をまとめると以下になります。

//アドレスを扱う
int *pointer = &hensuu;

//アドレス先の値を扱う
*pointer = 10;

//アドレスを扱う
pointer = &hensuu2;

ポインタ変数の宣言(初期化)時には、「*」を付けてアドレスにアクセスします。
それ以外で「*」を付けると、アドレス先の値を扱います。
「*」を付けないとアドレスを扱います。

宣言時と代入時とで感覚的に逆になるので注意しましょう。

複数のポインタ変数の宣言

ポインタ変数を複数同時に宣言する場合にも注意点があります。

//これはOK
int *pointer;

//これはOK
int* pointer;

//これもOK
int * pointer;

//これはOK
int *pointer1, *pointer2;

//これはNG
int* pointer1, pointer2;
//↓こう解釈される
int *pointer1, pointer2;

14行目のように宣言すると、それは16行目のように先頭の変数のみがポインタ変数となり、二つ目以降は通常のint型変数として宣言されてしまいます。
それが目的の場合はかまわないのですが、読みにくいですし避けたほうがいいでしょう。

NULLポインタ

ポインタはメモリ上の位置を示す変数ですが、「メモリ上のどこも指していない状態」のポインタ変数も存在します。
それをNULLポインタといいます。

NULLポインタはポインタ変数に「0」を代入することで作ることができます。

//NULLポインタ
int *pointer = 0;

int num = 123;

//変数numを指すポインタにする
pointer = &num;

//再びNULLポインタにする
pointer = 0;

あるいはNULLという定数を代入しても良いです。
NULLはstdio.hなどで「0」と定義されています。

int *pointer = NULL;

NULLはポインタであることを明示するために「((*void)0)」と定義されている場合もあります。
そのため、NULLは整数の0の代用ではなくNULLポインタとしてのみ使用すべきです。

当然ながら、メモリ上のどこも指していないのでそのままでは使用できません。
NULLポインタはポインタ変数がどこも指していないことを明示したい場合に使用されます。

ポインタ変数を宣言しただけで初期化も代入もしていない状態はNULLポインタではありません。
他の変数と同じく値は不定なので、何か値を代入するまでは使用できません。

//NULLポインタではなく
//初期化されていないだけ
//(値は不定)
int *pointer;