変数のアドレス
変数のアドレス
基本編で、変数についてとりあげました。実は、変数はコンピュータのメモリ中にあるため、アドレスが存在します。例えば、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); }
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)
![]() |
各変数は、メモリ空間の中に適切な大きさで割り振られている |
実行環境によって、変数のアドレスは変わるので、実行結果の数値はこの値と一致しないと思われます。 しかし、どの変数にもアドレスがあり、型に依存した固有のサイズがあるという事実は変わりません。
ポインタ
ポインタとは
このように、変数には値のほかに、その値を格納するアドレスがあることがわかりました。つまり、変数には値とアドレス、二つの側面があるのです。通常の変数は値を入れることを前提としています。
しかし、C言語には、アドレスを入れることを前提とした変数が存在します。それをポインタ変数もしくは、単にポインタと言います。では、そのポインタ変数を利用するにはどうしたらよいのでしょうか。ポインタ変数は例えば以下のように定義をします。
整数型ポインタ変数pの定義(intの場合)このように、先頭に*をつけると、その変数がポインタ変数であることを示すことができます。ではそもそも、ポインタ変数と普通の変数はどう違うのでしょうか。その違いをまとめておきましょう。(表2-1)
通常の変数 | ポインタ変数 | 解説 | |
---|---|---|---|
宣言 | int a; | int* p: | ポインタ変数は、変数の先頭に*をつける |
値 | a | *p | ポインタ変数で値を示すには、先頭に*をつける必要がある。 |
アドレス | &a | p | 通常の変数は、値を入れるが前提だが、ポインタ変数はアドレスを入れる |
この表から判る通り、ポインタ変数は通常の変数と違い、アドレスを入れることを前提とした変数です。したがって、ここにはなんらかの ほかの変数などのアドレスを入れることを前提とします。
サンプルプログラム
では、このポインタはどのように利用すればよいのでしょうか?まずはその例として、以下のプログラムを実行してみてください。
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 = 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で初期化ポインタに変数のアドレスを代入
続いてプログラムの流れに戻りましょう。見て判るとおり、このプログラムには、a,b,pという3種類のint型の変数が使われています。ただ、a,bが通常の値をとる変数であるのに対し、pはポインタ変数です。 そのため、このプログラムの代入処理の流れをまとめると、以下のようになります。(表2-2)
番号 | 処理 | 処理内容 | 意味 | a | b | *p |
---|---|---|---|---|---|---|
① | p = &a; | pに、aのアドレスを代入 | *pはaと同じものになる | 100 | 200 | 100 |
② | *p = 300; | *pに300を代入 | *pはaに等しいので、aが変わる | 300 | 200 | 300 |
③ | p = &b; | pに、bのアドレスを代入 | *pはbと同じものになる | 300 | 200 | 200 |
④ | *p = 400; | *pに400を代入 | *pはbに等しいので、bが変わる | 300 | 400 | 400 |
まず、①で、aのアドレスを取得することにより、pは、aとして振舞うことが可能になります。そのため、値である*pは、aと同じ値をとります。 次に、②で、*pに値を入れると、pには実体は無く、aをさしていることから、その値はaに反映されます。
同様に、③で、pにaのアドレスを代入すると、今度は、pはbとして振舞うことが可能になります。値である*pは、今度はbと同じ値をとっています。 更に、④で、*pに値を入れると、今度はbの値が変わります。これは②の時と同様で、*pが実際にはbの値となるからです。(図2-2)
![]() |
ポインタは、アドレスを変更することにより、他の変数になれる |
このように、ポインタ変数は、初期状態でそれ自身は値を持ちませんが、変数のアドレスを与えることにより、あたかもその変数であるかのごとく 振舞うことが出来るのです。したがって、このサンプルのように、変数名はpしかないのにもかかわらず、aやbといった、ほかの変数として振舞うことができるのです。
ポインタの注意点
なお、ポインタに変数のアドレスを設定するときは、原則的に同じ型のポインタ変数でするようにしましょう。例えば、「int a」であれば、「int *p」、 「double d」であれば、「double *pd」といったように、対応する型をあわせるようにしましょう。 型が違っても、コンパイルエラーにはなりませんが、実行時に致命的なエラーになる可能性があります。
変数のアドレスを入れるポインタと、変数の型は一致させるようにする。int a | → | int* p; OK | char *p; NG |
double d | → | double* pd; OK | int *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 = 2 b = 1
引数としてのポインタ
関数swap()では、アドレスを与えられた二つの変数の値を入れ替えています。通常、今までのように値だけを与えるタイプの今までのような関数 であれば、このような処理は出来ませんでしたが、引数にポインタを与えることにより、アドレスを与えた変数の値を変更することが出来ます。
また通常、変数は一つの戻り値しか返すことが出来ませんが、このように引数をポインタとして渡すことにより、実質的に複数の戻り値をもつ、もしくは 引数を戻りちと同じように扱うことが出来る関数を作ることが可能なのです。(図2-3)
![]() |
num1,num2はa,bのアドレスを受け取り、それぞれの変数のように振舞う |
このように、一見複雑ですが、大変便利な使い方が出来るのがポインタだということが判りました。ただ、ポインタの真骨頂は、配列や構造体(こうぞうたい)と セットにして使われたときに発揮されます。次は、配列のケースについて見てみましょう。
NULLポインタへのアクセス
ポインタ利用時に気をつけること
さて、すでに述べたとおり、ポインタ変数は、NULLで初期化する必要があります。しかし、気をつけてほしいのは、初期化したまま何らかの変数のアドレスを設定しなかった 場合、コンパイルエラーは出ませんが、実行時エラーが出てプログラムは止まってしまいます。以下のサンプルを実行してみてください。
listex2-4:main.c#include <stdio.h> void main(){ // ポインタをNULLで初期化。 int *p = NULL; // アドレスを指定しないまま値を代入 *p = 1; }
コンパイルは通りますが、これを実行すると、プログラムが異常終了します。NULLに限らず、ポインタ変数は、該当するアドレスに値が存在しない場合は、このような エラーになることがよくありますので、注意が必要です。