ポインタ
ポインタとは
プログラム中で変数を使用すると、その変数の値はメモリ上に一時的に保存されます。
変数だけでなく、関数を定義すればその関数の実装もメモリ上に保存され、必要に応じて呼び出されます。
こういったメモリ上に展開されたデータにアクセスするには、そのデータがメモリ上のどこにあるのかを知っておかなければなりません。
通常、プログラマは変数がメモリ上のどこに存在するかを意識することなく変数を使用することができます。
これはプログラマが意識しないところでプログラミング言語が「上手いこと」やってくれているのです。
しかし、あえて明示的にメモリ上の場所を意識してプログラミングをすることもできます。
それがポインタという概念です。
変数のメモリ上の位置の確認
まずは以下のサンプルプログラムを見てください。
#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進数表示にしています。
(x
は16進数表記、8
は8桁表示、0
は8桁に足りない場合は空きを0で埋める、という意味です)
(ポインタの表示は%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」に書き換えられていることがわかります。
ポインタとは「指し示すもの」
ポインタなど使わずとも、普通に変数を使えば値の読み書きはできるのだから、あまり意味がないように思えるかもしれません。
ポインタが有効に使える場面はおいおい説明しますが、まずはポインタ変数と通常の変数との違いを明確にしておきましょう。
#include <stdio.h>
int main()
{
int kazu1 = 10;
int *pointer = &kazu1; //kazu1のアドレスを代入
printf("*pointer: %d\n", *pointer); //10
int kazu2 = *pointer; //kazu1の値を代入
*pointer = 20; //kazu1に20を代入
printf("*pointer: %d\n", *pointer); //20
pointer = &kazu2; //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型なら整数値です。
その値は変数宣言によって確保されたメモリ領域に保存されます。
ポインタ変数も「値」を持ちます。
その値はアドレスです。
値(アドレス)は変数宣言によって確保されたメモリ領域に保存されます。
ポインタ変数は、アドレスが指す先のメモリ領域の値を読み書きできます。
ポインタ変数のデータ型は、アドレスが指すメモリ領域にある値のデータ型に*
を加えたものになります。
(ポインタの先の値がint型ならポインタ変数はint*型)
ポインタ変数の注意点
間接演算子の有無
ポインタ変数に対する間接演算子の有無をまとめると以下になります。
//アドレスを扱う
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型変数として宣言されてしまいます。
それが目的の場合はかまわないのですが、読みにくいですし避けたほうがいいでしょう。
ポインタ変数とconst
ポインタ変数もconstで定数化することができます。
(constについては定数とマクロを参照)
ポインタ変数のconst
キーワードは、アドレス演算子(*
)の左側に書くか右側に書くかで「何を変更不可にするか」が変わります。
int num = 0;
//普通のポインタ変数
int* p1 = #
*p1 = 1;
p1 = NULL;
//値を変更できない
const int* p2 = #
//*p2 = 1;
p2 = NULL;
//ポインタのアドレスを変更できない
int* const p3 = #
*p3 = 1;
//p3 = NULL;
//値もアドレスも変更できない
const int* const p4 = #
//*p4 = 1;
//p4 = NULL;
constとアドレス演算子との位置関係が変わらないのなら意味は同じです。
int num = 0;
//同じ意味
const int* p1 = #
int const* p2 = #
//同じ意味
const int* const p3 = #
int const* const p4 = #
NULLポインタ
ポインタはメモリ上の位置を示す変数ですが、「メモリ上のどこも指していない状態」のポインタ変数も存在します。
それをNULLポインタといいます。
NULLポインタはポインタ変数に「0」を代入することで作ることができます。
//NULLポインタ
int *pointer = 0;
int num = 123;
//変数numを指すポインタにする
pointer = #
//再びNULLポインタにする
pointer = 0;
あるいはNULL
という定数を使用することもできます。
NULLはstdio.h
などで「0」と定義されています。
int *pointer = NULL;
定数NULLはポインタであることを明示するために((*void)0)
と定義されている場合もあります。
そのため、定数NULLは整数の0の代用ではなくNULLポインタとしてのみ使用すべきです。
当然ながら、メモリ上のどこも指していないのでそのままでは使用できません。
NULLポインタはポインタ変数がどこも指していないことを明示したい場合に使用されます。
ポインタ変数を宣言しただけで初期化も代入もしていない状態はNULLポインタではありません。
他の変数と同じく値は不定なので、何か値を代入するまでは使用できません。
//NULLポインタではなく
//初期化されていないだけ
//(値は不定)
int *pointer;