ポインタと配列
| 対応: | C89(1989) |
|---|
C言語では配列とポインタは密接な関係にあります。配列名は先頭要素へのポインタとして扱われ、ポインタに整数を加算することで次の要素のアドレスに移動できます(ポインタ演算)。
構文
// 配列名はそのまま先頭要素へのポインタとして使えます。
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // &arr[0] と同じです。
// インデックス記法とポインタ記法は等価です。
arr[i] // インデックス記法
*(arr + i) // ポインタ記法
// ポインタのインクリメントで次の要素に進みます。
p++; // sizeof(int) バイト分だけアドレスが進みます。
p += n; // n要素分だけアドレスが進みます。
// ポインタを介して関数に配列を渡します。
void func(int *arr, int size);
void func(int arr[], int size); // 上と同じ意味です。
ポインタと配列の対応
| 記法 | 同等な表現 | 概要 |
|---|---|---|
| arr | &arr[0] | 配列先頭要素のアドレスです。 |
| *arr | arr[0] | 配列先頭要素の値です。 |
| arr + i | &arr[i] | i番目の要素のアドレスです。 |
| *(arr + i) | arr[i] | i番目の要素の値です。 |
| p++ | 次の要素へ移動 | 型のサイズ分だけアドレスを増やします。 |
| p2 - p1 | 要素数の差 | 2つのポインタが何要素分離れているかを返します。 |
サンプルコード
sample_pointer_array.c
#include <stdio.h>
int sum(int *arr, int size) {
int total = 0;
for (int i = 0; i < size; i++) {
total += arr[i];
}
return total;
}
void print_array(const int *p, int size) {
const int *end = p + size;
while (p < end) {
printf("%d ", *p);
p++;
}
printf("\n");
}
int main(void) {
int scores[] = {80, 75, 90, 65, 88};
int n = sizeof(scores) / sizeof(scores[0]);
printf("scores[2] = %d\n", scores[2]); /* 『90』と出力されます。 */
printf("*(scores+2) = %d\n", *(scores+2)); /* 『90』と出力されます。 */
int *p = scores;
printf("*p = %d\n", *p); /* 先頭要素: 80 */
p++;
printf("*p = %d\n", *p); /* 次の要素: 75 */
printf("合計: %d\n", sum(scores, n)); /* 『合計: 398』と出力されます。 */
print_array(scores, n); /* 『80 75 90 65 88』と出力されます。 */
/* 2次元配列では matrix[i][j] と *(*(matrix+i)+j) は等価です。 */
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
printf("matrix[1][2] = %d\n", *(*(matrix + 1) + 2)); /* 『6』と出力されます。 */
return 0;
}
コンパイルして実行すると次のようになります。
gcc pointer_array.c -o pointer_array ./pointer_array scores[2] = 90 *(scores+2) = 90 *p = 80 *p = 75 合計: 398 80 75 90 65 88 matrix[1][2] = 6
ポインタ演算でバッファを走査する
ポインタ演算を使うと、関数に渡された配列の任意の部分を効率的に処理できます。最小値・最大値の検索やフィルタリングに応用できます。
pointer_scan.c
#include <stdio.h>
/* ポインタ演算で配列の最大値を返します。 */
int find_max(const int *p, int size) {
const int *end = p + size;
int max = *p++;
while (p < end) {
if (*p > max) max = *p;
p++;
}
return max;
}
/* 特定の値を持つ要素のアドレスを返します(見つからない場合は NULL)。 */
const int *find_val(const int *p, int size, int target) {
for (int i = 0; i < size; i++) {
if (p[i] == target) return &p[i];
}
return NULL;
}
int main(void) {
int hp[] = {450, 380, 520, 310, 490};
int n = (int)(sizeof(hp) / sizeof(hp[0]));
printf("最大HP: %d\n", find_max(hp, n));
const int *found = find_val(hp, n, 520);
if (found) {
printf("520 はインデックス %d にあります。\n", (int)(found - hp));
}
return 0;
}
コンパイルして実行すると次のようになります。
gcc pointer_scan.c -o pointer_scan ./pointer_scan 最大HP: 520 520 はインデックス 2 にあります。
よくあるミス
よくあるミス: 関数内で sizeof が正しく動かない
関数に配列を渡すと配列はポインタに変換されます。関数内で sizeof を使っても配列全体のサイズではなくポインタのサイズが返ります。
pointer_sizeof_ng.c
#include <stdio.h>
void wrong_size(int arr[]) {
/* NG: ポインタのサイズ(8バイト)が返る。配列全体ではない */
printf("NG sizeof: %zu\n", sizeof(arr));
}
int main(void) {
int arr[5] = {1, 2, 3, 4, 5};
printf("正しい sizeof: %zu\n", sizeof(arr)); /* 20 バイト(int 4 × 5) */
wrong_size(arr);
return 0;
}
修正後は次の通りです。
gcc pointer_sizeof_ng.c -o pointer_sizeof_ng ./pointer_sizeof_ng 正しい sizeof: 20 NG sizeof: 8
pointer_sizeof_ok.c
#include <stdio.h>
/* OK: サイズを別引数として渡す */
void print_sum(const int *arr, int size) {
int sum = 0;
for (int i = 0; i < size; i++) sum += arr[i];
printf("合計: %d\n", sum);
}
int main(void) {
int arr[5] = {1, 2, 3, 4, 5};
int n = (int)(sizeof(arr) / sizeof(arr[0]));
print_sum(arr, n); /* 『合計: 15』と出力されます。 */
return 0;
}
修正後は次の通りです。
gcc pointer_sizeof_ok.c -o pointer_sizeof_ok ./pointer_sizeof_ok 合計: 15
概要
配列名はポインタのように使えますが、本質的な違いがあります。配列名はそれ自体がアドレスであり、別のアドレスを代入することはできません。一方、ポインタ変数は任意のアドレスを格納でき、インクリメントや代入が可能です。
関数に配列を渡すと、配列のサイズ情報は失われます。関数内で『sizeof(arr)』を使っても配列全体のサイズではなくポインタのサイズが返ります。配列サイズは必ず別引数として渡してください。
ポインタの基本については『ポインタの基本』を参照してください。文字列(char配列)とポインタの関係は『ポインタと文字列』で解説しています。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。