8.
ポインタ
ポインタ
・*:..☆ プロローグ ☆..:*・
むかしむかし、あるところに、とても見栄っ張りで、綺麗な服が大好きな王様がいました。
ある日の事、王様のところに二人の仕立て屋がやってきました。
「わたしたちは、とても美しい服を持っています。その服はポインタと言って、とても不思議な服で、実態ではないのです。ポインタ変数に変数のアドレスを代入することによって、ポインタ変数はその変数を指し示すことになります。変数のアドレスを取得するにはアドレス演算子‘&’を使用します。ポインタ変数から...」
「わかった、わかった。もうよい。そのくらいの事は知っておる。それより、さっさと服を出しなさい。」
王様は頭が悪かったので、仕立て屋の言っていることがさっぱり理解できませんでした。しかし、見栄っ張りだったので知っている振りをしたのです。
もう一人の仕立て屋が王様の前にポインタを出しました。ポインタは、ただの数字のようで、とても服には見えませんでした。
「なんだこれは、ただの数字じゃないか。服を出せと言ったのだ。」
仕立て屋は答えました。
「王様もお人が悪い。ポインタですから間接演算子‘*’を使用しないと実態は見えません。ポインタの扱いを知らない愚か者には見えないのです。」
「そうか、そうだったな。関節?...演算子が必要だったな。もちろんポインタとはそういうものだ。」
王様は、また見栄を張って知っている振りをしました。
「よし、その服を買うとしよう。ポインタをそこに置いて下がりなさい。大臣、代金を払ってやりなさい。」
二人の仕立て屋は、喜んで代金を受け取り去っていきました。
「うまくいったな、相棒。ポインタを売っただけで大儲けさ。実態の服を見せてやっても良かったんだが、あの王様、さっぱり分からないようで、それまでもなかった。ポインタを理解してない奴を騙すなんて簡単なものさ。」
ポインタを扱うのはC言語が高速で効率の良いプログラムを生成できる要因のひとつであり、また一方で、C言語を分かりにくくしている要因のひとつでもあります。
8.1. メモリのイメージ
メモリのイメージ
メモリは、1 バイト毎に付けられたアドレスによって管理されます。次のような図で表現されます。
00000000 |
|
00000001 |
|
00000002 |
|
00000003 |
|
: |
: |
: |
: |
0000F031 |
|
0000F032 |
|
0000F033 |
|
0000F034 |
|
: |
: |
: |
: |
通常、アドレスは 16 進数で表記されます。
C言語では、次に説明するポインタによってメモリの内容を直接操作することが出来ます。
メモリの確保
メモリの確保
1バイトの変数をひとつ、宣言します。
char value;
プログラムが実行されると、この宣言によりメモリ上に 1 バイトの領域が確保されます。メモリ上のどこに確保されるかは実行時に決定されます。
: |
: |
: |
: |
: |
: |
0000F032 |
× |
: |
: |
: |
: |
仮にアドレス 0000F032 に確保されたとします。
value = 12;
を実行すると、0000F032の値が 12 に設定されます。
変数名 value はアドレス 0000F032 に格納されている数値を示しています。
: |
: |
: |
: |
: |
: |
0000F032 |
12 |
: |
: |
: |
: |
int 型が 4 バイトであるとすれば、int 型の変数は次のように確保されます。
: |
: |
: |
: |
: |
: |
0000F032 |
× |
|
× |
|
× |
|
× |
: |
: |
: |
: |
8.2. ポインタ変数
ポインタ変数
メモリのアドレスを保持する変数が、ポインタ変数です。
アドレスは整数なので、整数型の変数に代入することもできますが、ポインタ変数はアドレスを扱うためだけに用意されており、整数型とは異なる機能を持っています。
ポインタ変数は次のように宣言します。
char* pvalue;
プログラムが実行されると、この宣言により 1 バイトの領域を示すアドレスを格納する為の領域がメモリ上に確保されます。
ポインタ変数のサイズは実行環境によって異なりますが、ここでは 4 バイトとしています。
: |
: |
: |
: |
: |
: |
0000F033 |
× |
|
× |
|
× |
|
× |
: |
: |
: |
: |
ポインタ変数を宣言するには、データ型の後(変数名の前)にアステリスク“*”を記述します。char 型へのポインタであれば、char*、int 型へのポインタであれば、int* です。
これらは、char* 型、int* 型というデータ型であると考えてください。
変数のアドレス
変数のアドレス
演算子“&”は、その変数領域のアドレス(ポインタ)を出します。
したがって、次のような式を書くことが出来ます。
char value;
char* pvalue;
pvalue = &value;
変数 pvalue は変数 value のアドレスを示すことになります。
: |
: |
|
0000F032 |
value |
|
0000F033 |
pvalue 0000F032
|
: |
: |
|
: |
: |
|
: |
: |
|
演算子“*”は、ポインタから、そのポインタが指し示す変数を参照する演算子です。
次のコードでは、変数 value に、直接は値を代入していません。しかしながら、value の値は 12 になります。
int value;
int* pvalue;
pvalue = &value;
*pvalue = 12;
printf( "value=%d\n", value );
また、次のコードでは、ポインタ変数 pvalue を使用して value の値を表示しています。
int value;
int* pvalue;
pvalue = &value;
value = 12;
printf( "value=%d\n", *pvalue );
アドレスを表示するには、printfで、%p を使用します。
int value;
printf( "value:%p\n", &value );
8.3. 配列とポインタ
配列とポインタ
次のように記述すると、メモリ上に複数の連続した領域を確保します。
char array[100];
: |
: |
: |
: |
: |
: |
0000F032 |
: : 100バイト : :
|
: |
: |
: |
: |
: |
: |
ここで、配列 array の要素にアクセスするには角カッコ [ ] と添え字を使用するのでした。
それでは、角カッコ [ ] と添え字の無い“array”は、何を示しているのでしょうか?
実は、添え字の無い array は配列の先頭アドレス、つまり、ポインタなのです。
よって、次のようなコードを書くことが出来ます。
int array[100];
int* parray;
parray = array;
これは、次のコードと同等です。
int array[100];
int* parray;
parray = &array[0];
ポインタによる配列のアクセス
ポインタによる配列のアクセス
配列のアドレスが得られることから、ポインタを使用して配列の要素にアクセスすることが出来ます。
char hello[100];
char* phello = hello;
*phello = 'H';
*(phello + 1 ) = 'e';
*(phello + 2 ) = 'l';
*(phello + 3 ) = 'l';
*(phello + 4 ) = 'o';
*(phello + 5 ) = 0;
printf( hello );
“phello + n”は、phello の n 個分、後のアドレスを計算します。これは、配列 hello の n 番目の要素のアドレスです。
さらに、このコードは、ポインタ変数 phello を使用せず、次のように書くことが出来ます。
char hello[100];
*hello = 'H';
*(hello + 1 ) = 'e';
*(hello + 2 ) = 'l';
*(hello + 3 ) = 'l';
*(hello + 4 ) = 'o';
*(hello + 5 ) = 0;
printf( hello );
逆に、ポインタ変数に対し、添え字を使用することも出来ます。
char hello[100];
char* phello = hello;
phello[0] = 'H';
phello[1] = 'e';
phello[2] = 'l';
phello[3] = 'l';
phello[4] = 'o';
phello[5] = 0;
printf( phello );
ポインタの演算
ポインタの演算
前の例では、大きさが 1 バイトである char 型の配列を使用しました。アドレスに1を加えることで、配列の次の要素のアドレスになるという事は、理解しやすかったのではないでしょうか。
例えば、配列の先頭アドレスが 0000FF03 であったとすると、次の要素のアドレスは 0000FF04 であって、その次は 0000FF05 というように、順番に1ずつ増えると考える事が出来ます。
それでは、int 型の場合はどうなるのでしょうか。
int が 4 バイトであるとすると、各要素のアドレスは 1 ずつ増えるわけではありません。1つの要素に 4 バイトを使用するからです。
とすると、ポインタを使用して配列にアクセスするには、常に要素のサイズを考慮しなければならないように思えますが、実際には、そうではありません。
ポインタの演算は、そのポインタが指し示すデータ型に対応しています。
ポインタに 1 を加えると、単に実際のアドレス値に 1 が加えられるのではなく、データ型に対応し、次の要素のアドレスを示します。
つまり、配列の添え字と同じように計算されます。
次のようにアドレスを表示することによって、実際の演算結果が確認できます。
char achar[3];
int aint[3];
printf( "achar[0]:%p\n", achar );
printf( "achar[1]:%p\n", achar + 1 );
printf( "achar[2]:%p\n", achar + 2 );
printf( "aint[0] :%p\n", aint );
printf( "aint[1] :%p\n", aint + 1 );
printf( "aint[2] :%p\n", aint + 2 );
以下は、ポインタ演算を使用した例です。ポインタ変数 p に 1 を加えることによって、配列の要素を順番に表示します。
int numbers[] = { 12, 85, 26 };
int* p = numbers;
printf( "%d\n", *p );
p += 1;
printf( "%d\n", *p );
p += 1;
printf( "%d\n", *p );
インクリメント演算子を使用した例です。ポインタ変数をインクリメントすることによって、文字を 1 文字ずつ表示します。
char hello[] = "Hello World\n";
char* p = hello;
while( *p )
printf("%c", *p++);
最後の行、*p++ のような書き方は、よく用いられます。*p の値が使用されてから、p がインクリメントされます。
ポインタ配列
ポインタ配列
ポインタ変数も他の変数と同じように、配列にすることができます。
ポインタ配列の宣言は、次のように記述します。
int* a[12];
この場合の配列の要素は、int 型へのポインタです。
よく使用される例として、文字列の配列があります。
文字列は char 型の配列ですから、文字列の配列は、配列の配列、つまりポインタ配列となるわけです。
文字列の配列は、次のように初期化することができます。
char* signal[] = { "red", "blue", "yellow" };
int i;
for( i = 0 ; i < 3 ; i++ )
printf( "%s\n", signal[i] );
8.4. まとめ
まとめ
配列とポインタは、密接な関係があります。
配列はポインタによって示され、要素にアクセスするには、添え字を使う方法と、ポインタ演算を使用する方法があると考えて良いでしょう。
以下に、ポインタの使用方法を、まとめます。
ポインタ変数型 |
データ型の後ろに*を付ける。 例:char* pText; int* pValue;
|
変数のアドレス |
‘&’演算子を使用する。
例:
pValue = &Value;
|
ポインタの指し示す先 |
‘*’演算子を使用する。例:
*pValue = 126;
x = *pValue;
|
配列の要素 |
添え字を使用する方法と、ポインタ演算による方法がある。例:n番目の要素を示す。
x = array[n];
x = *( array + n );
|
C言語以外の言語の多くはポインタを使用しません。しかし、プログラム言語とメモリとの関係を理解する上で重要な事項です。