今回は《配列とポインタの関係について学ぼう》というテーマで記事をまとめます。
C言語における『鬼門』である「ポインタ」と、以前解説した「配列」は密接な関係にあります。しっかり学んでいきましょう!
ポインタの基本を習得していない方は、まずは下記の記事を参考に学習をしてみてください。
尚、現在執筆中のC言語入門記事は以下の人を対象としています。
- プログラミング未経験の人
- C言語プログラミングの学習をこれから始めたい人
- C言語プログラミングの学習を始めたばかりの人
ポインタと配列の関係について学ぼう
ポインタと配列
ポインタを利用して配列の各要素への値の代入・取得方法を解説します。
<【独学C言語入門⑩】配列について学ぼう>にて、配列は「同じ型の変数の塊」という解説をしたかと思いますが、1つのポインタ変数がアクセスできるのは要素1つのみとなるので注意してください。
配列変数のアドレス取得
まずは、配列変数のアドレス取得方法から解説します。
配列変数のアドレス取得は、大きく分けて次の2種類の方法があります。
- 先頭要素のアドレスを取得
- 特定の要素のアドレスを取得
まずは、先頭要素のアドレスを取得する方法について解説します。書式は次のようになります。
ポインタ変数名 = 配列変数名;
配列変数の先頭要素のアドレスを取得する場合、“&”を付与する必要はありません。
次に、特定の要素のアドレスを取得する方法を解説します。こちらは添字と“&”を使用します。書式は次のようになります。
ポインタ変数名 = &(配列変数名[添字]);
参照先要素への値の代入・取得
ポインタ変数を介した配列要素への値の代入・取得には大きく分けて次の2通りの方法があります。
- ポインタ変数の前に"*"を付与
- ポインタ変数に添字を付与
まずは、ポインタ変数の前に"*"を付与してアクセスする方法について解説します。書式は次のようになります。
*ポインタ変数名 = 値;
通常の変数に対するポインタ変数と使い方が同じなのでわかりやすいですね。
次に、ポインタ変数に添字を付与してアクセスする方法を解説します。こちらは添字と“&”を使用します。書式は次のようになります。
ポインタ変数名[添字] = 値;
ポインタ変数に添字を用いる場合の注意点は、指している要素からの相対位置となることです。
int ary[5] = { 1, 2, 3, 4, 5 }; int* ptr = NULL; ptr = &(ary[2]); /* ary[2] を参照先に設定 */ ptr[2] = 10; /* 参照先変数から2つ先の ary[4] に値が代入される */
参照先の移動
特定の要素を参照しているポインタ変数に対して、加減算を行うと参照先を移動させることができます。
インクリメントすると次の要素へ、デクリメントすると前の要素へ移動します。数値を加算するとその分だけ先の要素へ移動し、減算するとその分だけ前の要素へ移動します。
ポインタ変数名++; /* 参照先の次の要素へ移動 */ ポインタ変数名--; /* 参照先の前の要素へ移動 */ ポインタ変数 += 数値; /* 参照先から数値の分だけ先の要素へ移動 */ ポインタ変数 -= 数値; /* 参照先から数値の分だけ前の要素へ移動 */
少しわかりにくいと思うので、具体例も載せておきます。
int ary[5] = { 1, 2, 3, 4, 5 }; int* ptr = NULL; ptr = ary; /* 先頭要素を参照 */ ptr++; /* 参照先が ary[1] に移動 */ ptr--; /* 参照先が ary[0] に移動 */ ptr += 4; /* 参照先が ary[4] に移動 */ ptr -= 2; /* 参照先が ary[2] に移動 */
サンプル
それでは、サンプルを用意したので実行してみてください。
#include <stdio.h> void main(){ int i = 0; int ary[9] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int* ptr = NULL; ptr = ary; /* 先頭要素を参照 */ printf("*ptr = %d\n", *ptr); for( i = 0; i < 9; i++ ) { printf("ary[%d] = %d\n", i, ary[i] ); } printf("\n"); ptr[6] = 100; /* ary[6] に値を代入 */ ptr++; /* 参照先が ary[1] に移動 */ *ptr = 10; ptr--; /* 参照先が ary[0] に移動 */ *ptr = 20; ptr += 4; /* 参照先が ary[4] に移動 */ *ptr = 30; ptr -= 2; /* 参照先が ary[2] に移動 */ *ptr = 40; ptr[6] = 200; /* 参照先が ary[2] なので ary[8] に値を代入 */ for( i = 0; i < 9; i++ ) { printf("ary[%d] = %d\n", i, ary[i] ); } }
【応用】ポインタ変数の加減算とアドレス値
ポインタを加減算するとアドレス値はどうなる?
ポインタ変数は加減算を行うことで、指定した値だけ先または前の要素へ移動できると解説しましたね。
さて、それではポインタ変数を加減算した場合、ポインタ変数に格納されているアドレス値はどのように変化しているのでしょう?
単純に考えれば、加減算した値で変化すると思えますね(例えば、インクリメントした場合はアドレス値も+1される。)。
それでは、次のソースを実行して確認してみましょう。
#include <stdio.h> void main(){ int i = 0; int ary[9] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int* ptr = NULL; ptr = ary; /* 先頭要素を参照 */ printf("*ptr = %d, %p\n", *ptr, ptr); ptr++; printf("*ptr = %d, %p\n", *ptr, ptr); ptr++; printf("*ptr = %d, %p\n", *ptr, ptr); }
アドレス値はインクリメントを行う度に4バイトずつ加算されていますね。
実は、ポインタ変数のアドレス値に加減算を行うと『加減算した値×参照先変数の型サイズ(バイト数)』の分だけアドレス値が変化します。
今回のソースでは、『int型のポインタ』なので参照先変数の型サイズは4バイトである為、インクリメントを行う度に4バイトずつ加算されたワケです。
尚、変数の型サイズに関しては、<【独学C言語入門⑤】「変数」の基礎を学ぼう>の<基本的な型一覧>をご参照ください。
なぜ単純な計算が行われないの?
なぜ、ポインタ変数のアドレス値は単純な計算が行われないのでしょうか?
その答えは「配列要素の型サイズを意識してアドレス値を計算するのは億劫」だからだと考えています。
これだけだと、全然理解できない!という方が多いと思います。この理由を説明するには配列の仕様を知る必要があります。
それでは、配列の仕様から順番に解説していきます。
配列要素とメモリ領域の関係
配列を定義すると、メモリ上に『変数の型サイズ×配列の要素数』の分だけの連続した領域が確保されます。
つまり、『要素数3のint型配列』を定義すると、4バイト×3(要素数)=20バイトの分のメモリ領域が確保されるというワケです。
ポインタの指すことができるアドレスとできないアドレス
ポインタ変数には参照先変数のメモリ領域の内、先頭のアドレス値を格納しておく必要があります。
配列要素の型サイズを意識してアドレス値を計算するのは億劫
もし、単純な計算が行われた場合、ポインタ変数をインクリメントすると適切なアドレス値が格納されなくなります(char型は1バイト変数なので問題ないですが)。
すると、適切なアドレス値を格納するには毎回『参照先変数の型サイズ』を意識する必要があり、かなり億劫ですね。
だからこそ、『加減算した値×参照先変数の型サイズ(バイト数)』の分だけアドレス値が変化する仕様としたのだと考えられます。
そのおかげで、開発者は『配列要素の型サイズ』を意識せずにポインタを使って配列要素に参照できる(「次の要素」や「3個前の要素」など)のです。
次回も「ポインタ」が続きます
今回は「ポインタと配列の関係」について学んでもらいました。
次回も引き続き「ポインタ」について解説したいと思います。ポインタの基礎編③として、構造体に対する使用方法を解説します。
ご期待ください。