Top >C言語入門 とりあえずのC言語

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言語以外の言語の多くはポインタを使用しませんしかし、プログラム言語とメモリとの関係を理解する上で重要な事項です

PAPER BOWL
NEZEN