変数のアドレス

変数のアドレス

基本編で、変数についてとりあげました。実は、変数はコンピュータのメモリ中にあるため、アドレスが存在します。例えば、aという変数があると、&aとすることに より、変数のアドレスを取得することができます。これにより、変数の値がメモリ中のどこに存在するのかを知ることができます。

サンプルプログラム

では、実際に変数のアドレスを取得するプログラムを作成してみましょう。

listex2-1:main.c
#include <stdio.h>

void main(){
	int a = 100;		//	int型の変数
	double b = 123.4;	//	double型の変数
	float c = 123.4f;	//	float型の変数(数値の後ろにfつける)
	char d = 'a';		//	char型の変数
	printf("aの値は%d、大きさは%dbyte、アドレスは0x%x¥n",a,sizeof(int),&a);
	printf("bの値は%f、大きさは%dbyte、アドレスは0x%x¥n",b,sizeof(double),&b);
	printf("cの値は%f、大きさは%dbyte、アドレスは0x%x¥n",c,sizeof(float),&c);
	printf("dの値は%c、大きさは%dbyte、アドレスは0x%x¥n",d,sizeof(char),&d);
}
実行結果
aの値は100、大きさは4byte、アドレスは0x28fddc
bの値は123.400000、大きさは8byte、アドレスは0x28fdcc
cの 値は123.400002、大きさは4byte、アドレスは0x28fdc0
dの値はa、大きさは1byte、アドレスは0x28fdb7

sizeof演算子

このプログラムの中に出てくるsizeof(サイズオブ)演算子は、変数や型のメモリのサイズを取得する演算子です。()の中に変数や、型を入れれば、 そのサイズをバイト単位で取得することが可能です。

sizeof演算子の使用方法
sizeof(int)   ← int型のサイズを取得
sizeof(a)   ← 変数aのサイズを取得

このプログラムを実行すれば、a,b,c,dの4つの変数の、メモリ空間上で占めるメモリの大きさをバイト数で、そしてそのアドレスを取得できます。 a,b,c,dは、それぞれ違う型の変数ですが、アドレスは整数型として取得されます。printf()関数の中で%x書式で表示すれば、その結果が16進数として表示されます。

実行結果から、各変数には、固有のアドレスとサイズがあることがわかります。(図2-1)

図2-1.変数のアドレス
変数のアドレス
各変数は、メモリ空間の中に適切な大きさで割り振られている

実行環境によって、変数のアドレスは変わるので、実行結果の数値はこの値と一致しないと思われます。 しかし、どの変数にもアドレスがあり、型に依存した固有のサイズがあるという事実は変わりません。

ポインタ

ポインタとは

このように、変数には値のほかに、その値を格納するアドレスがあることがわかりました。つまり、変数には値とアドレス、二つの側面があるのです。通常の変数は値を入れることを前提としています。

しかし、C言語には、アドレスを入れることを前提とした変数が存在します。それをポインタ変数もしくは、単にポインタと言います。では、そのポインタ変数を利用するにはどうしたらよいのでしょうか。ポインタ変数は例えば以下のように定義をします。

整数型ポインタ変数pの定義(intの場合)
int *p;

このように、先頭に*をつけると、その変数がポインタ変数であることを示すことができます。ではそもそも、ポインタ変数と普通の変数はどう違うのでしょうか。その違いをまとめておきましょう。(表2-1)

表2-1:通常の変数とポインタ変数の比較(intの場合)
通常の変数ポインタ変数解説
宣言int a;int* p:ポインタ変数は、変数の先頭に*をつける
a*pポインタ変数で値を示すには、先頭に*をつける必要がある。
アドレス&ap通常の変数は、値を入れるが前提だが、ポインタ変数はアドレスを入れる

この表から判る通り、ポインタ変数は通常の変数と違い、アドレスを入れることを前提とした変数です。したがって、ここにはなんらかの ほかの変数などのアドレスを入れることを前提とします。

サンプルプログラム

では、このポインタはどのように利用すればよいのでしょうか?まずはその例として、以下のプログラムを実行してみてください。

listex2-2:main.c
#include <stdio.h>

void show(int,int,int);

void main(){
	int a = 100;	//	整数型変数a
	int b = 200;	//	整数型変数b
	int *p = NULL;	//	整数型のポインタ変数p
	p = &a;	//	pにaのアドレスを代入
	show(a,b,*p);
	*p = 300;	//	*pに値を代入
	show(a,b,*p);
	p = &b;	//	pにbのアドレスを代入
	show(a,b,*p);
	*p = 400;	//	*pに値を代入
	show(a,b,*p);
}

void show(int n1,int n2,int n3){
	printf("a = %d b = %d *p = %d\n",n1,n2,n3);
}
実行結果
a = 100 b = 200 *p = 100
a = 300 b = 200 *p = 300
a = 300 b = 200 *p = 200
a = 300 b = 400 *p = 400

NULLによる初期化

まず、ポインタ変数pにNULL(ヌル)を入れて初期化しています。NULLは、C言語で標準的に用いられる定数で、数値でいえば0を意味しますが、通常ポインタ変数はNULLで初期化するという習慣になっていますので、覚えておきましょう。

ポインタ変数をNULLで初期化
int* p = NULL;

ポインタに変数のアドレスを代入

続いてプログラムの流れに戻りましょう。見て判るとおり、このプログラムには、a,b,pという3種類のint型の変数が使われています。ただ、a,bが通常の値をとる変数であるのに対し、pはポインタ変数です。 そのため、このプログラムの代入処理の流れをまとめると、以下のようになります。(表2-2)

表2-2:a,bとpの変化
番号処理処理内容意味ab*p
p = &a;pに、aのアドレスを代入*pはaと同じものになる100200100
*p = 300;*pに300を代入*pはaに等しいので、aが変わる300200300
p = &b;pに、bのアドレスを代入*pはbと同じものになる300200200
*p = 400;*pに400を代入*pはbに等しいので、bが変わる300400400

まず、①で、aのアドレスを取得することにより、pは、aとして振舞うことが可能になります。そのため、値である*pは、aと同じ値をとります。 次に、②で、*pに値を入れると、pには実体は無く、aをさしていることから、その値はaに反映されます。

同様に、③で、pにaのアドレスを代入すると、今度は、pはbとして振舞うことが可能になります。値である*pは、今度はbと同じ値をとっています。 更に、④で、*pに値を入れると、今度はbの値が変わります。これは②の時と同様で、*pが実際にはbの値となるからです。(図2-2)

図2-2.ポインタのイメージ
ポインタのイメージ
ポインタは、アドレスを変更することにより、他の変数になれる

このように、ポインタ変数は、初期状態でそれ自身は値を持ちませんが、変数のアドレスを与えることにより、あたかもその変数であるかのごとく 振舞うことが出来るのです。したがって、このサンプルのように、変数名はpしかないのにもかかわらず、aやbといった、ほかの変数として振舞うことができるのです。

ポインタの注意点

なお、ポインタに変数のアドレスを設定するときは、原則的に同じ型のポインタ変数でするようにしましょう。例えば、「int a」であれば、「int *p」、 「double d」であれば、「double *pd」といったように、対応する型をあわせるようにしましょう。 型が違っても、コンパイルエラーにはなりませんが、実行時に致命的なエラーになる可能性があります。

変数のアドレスを入れるポインタと、変数の型は一致させるようにする。
int aint* p; OKchar *p; NG
double ddouble* pd; OKint *dp; NG

ポインタと関数

サンプルプログラム

前述のように、ポインタには、他の変数になりきれるという非常に面白い特徴があります。これを利用して、以下のような面白い処理を行うことが出来ます。

listex2-3:main.c
#include <stdio.h>

//	変数の値入れ替えを行う関数
void swap(int*,int*);

void main(){
	int a = 1,b = 2;
    printf("a = %d b = %d¥n",a,b);
    swap(&a,&b);
    printf("a = %d b = %d¥n",a,b);
}

//	値の入れ替え
void swap(int* num1,int* num2){
	int temp = *num1;
	*num1 = *num2;
	*num2 = temp;
}
実行結果
a = 1 b = 2
a = 2 b = 1

引数としてのポインタ

関数swap()では、アドレスを与えられた二つの変数の値を入れ替えています。通常、今までのように値だけを与えるタイプの今までのような関数 であれば、このような処理は出来ませんでしたが、引数にポインタを与えることにより、アドレスを与えた変数の値を変更することが出来ます。

また通常、変数は一つの戻り値しか返すことが出来ませんが、このように引数をポインタとして渡すことにより、実質的に複数の戻り値をもつ、もしくは 引数を戻りちと同じように扱うことが出来る関数を作ることが可能なのです。(図2-3)

図2-3.swap関数で行われる処理
swap関数で行われる処理
num1,num2はa,bのアドレスを受け取り、それぞれの変数のように振舞う

このように、一見複雑ですが、大変便利な使い方が出来るのがポインタだということが判りました。ただ、ポインタの真骨頂は、配列や構造体(こうぞうたい)と セットにして使われたときに発揮されます。次は、配列のケースについて見てみましょう。

NULLポインタへのアクセス

ポインタ利用時に気をつけること

さて、すでに述べたとおり、ポインタ変数は、NULLで初期化する必要があります。しかし、気をつけてほしいのは、初期化したまま何らかの変数のアドレスを設定しなかった 場合、コンパイルエラーは出ませんが、実行時エラーが出てプログラムは止まってしまいます。以下のサンプルを実行してみてください。

listex2-4:main.c
#include <stdio.h>

void main(){
    //  ポインタをNULLで初期化。
	int *p = NULL;
	//	アドレスを指定しないまま値を代入
	*p = 1;
}

コンパイルは通りますが、これを実行すると、プログラムが異常終了します。NULLに限らず、ポインタ変数は、該当するアドレスに値が存在しない場合は、このような エラーになることがよくありますので、注意が必要です。