
はじめてのC言語 | 第13回
はじめに
前回は、ポインタの基本を学びました。
今回は、その続きとして「配列とポインタの関係」を整理します。
この回の目的は次の5点です。
- 配列名がどのように扱われるかを理解する
array[i]と*(array + i)が同じ要素を表すことを確認する- ポインタを使った配列走査の基本を学ぶ
- 配列を関数に渡すときの基本形を理解する
- 範囲外アクセスの危険性を理解する
配列名はどう扱われるのか
配列を宣言すると、複数の要素が連続した領域に並んで保存されます。
たとえば int numbers[3] = {10, 20, 30}; では、3個の int が並んで配置されます。
配列名 numbers は、多くの式の中で「先頭要素のアドレス」のように扱われます。
つまり、numbers は多くの場合 &numbers[0] と同じ場所を指します。
ただし、配列名そのものが普通のポインタ変数になるわけではありません。
初学者向けには、まず「配列名は先頭要素を指すように使える」と理解しておけば十分です。
先頭要素のアドレスを確認する
ソースコード
array_address.c という名前で保存します。
#include <stdio.h>
int main(void) {
int numbers[3] = {10, 20, 30};
printf("%p\n", (void *)numbers);
printf("%p\n", (void *)&numbers[0]);
return 0;
}実行手順
1. 作業ディレクトリに移動する
cd ~/Desktop2. コンパイルする
clang array_address.c -o array_address3. 実行する
./array_address実行結果の例:
0x16b2ff2a0
0x16b2ff2a0アドレスの値そのものは実行環境で変わることがあります。
重要なのは、2行が同じ場所を表していることです。
コードの読み方
numbers
printf("%p\n", (void *)numbers);ここでの numbers は、配列の先頭要素のアドレスとして使われています。
&numbers[0]
printf("%p\n", (void *)&numbers[0]);これは、配列の0番目の要素 numbers[0] のアドレスです。
そのため、多くの場合 numbers と同じ値になります。
array[i] と *(array + i) の関係
C言語では、配列の要素アクセスはポインタ計算と強く結びついています。
numbers[0]
*(numbers + 0)この2つは同じ要素を表します。
同様に、次も同じ意味です。
numbers[1]
*(numbers + 1)numbers + 1 は、先頭から1個先の int 要素を指すアドレスです。
1バイトではなく、int 1個分だけ進む点が重要です。
同じ値になることを確認する
サンプルコード
array_pointer_access.c という名前で保存します。
#include <stdio.h>
int main(void) {
int numbers[3] = {10, 20, 30};
printf("%d\n", numbers[0]);
printf("%d\n", *(numbers + 0));
printf("%d\n", numbers[1]);
printf("%d\n", *(numbers + 1));
return 0;
}実行結果:
10
10
20
20ポインタを使って配列を走査する
配列は添字だけでなく、ポインタを進めながら読むこともできます。
#include <stdio.h>
int main(void) {
int numbers[4] = {10, 20, 30, 40};
int *p = numbers;
int i;
for (i = 0; i < 4; i++) {
printf("%d\n", *(p + i));
}
return 0;
}実行結果:
10
20
30
40int *p = numbers; の意味
numbers はここでは先頭要素のアドレスとして使われます。
そのため、int *p = numbers; と書くと、p は numbers[0] を指すポインタになります。
*(p + i) の意味
p + i は、p から i 個先の要素を指します。
そこに * を付けると、その位置の値を取り出せます。
配列を関数に渡すときの基本
配列を関数に渡すとき、実際には先頭要素のアドレスが渡されます。
そのため、関数側ではポインタとして受け取る形になります。
#include <stdio.h>
void print_array(int *arr, int size) {
int i;
for (i = 0; i < size; i++) {
printf("%d\n", arr[i]);
}
}
int main(void) {
int numbers[4] = {10, 20, 30, 40};
print_array(numbers, 4);
return 0;
}実行結果:
10
20
30
40なぜ要素数も渡すのか
関数に渡された arr は、配列全体そのものではなく、先頭要素を指す情報として扱われます。
そのため、関数の中だけでは要素数が自動では分かりません。
このため、配列と一緒に要素数も別に渡す必要があります。
添字で受け取る書き方もできる
関数引数では、次のような書き方もよく使います。
void print_array(int arr[], int size)この書き方でも、関数の中では arr は配列そのものではなく、先頭要素を指すものとして扱われます。
初学者の段階では、int *arr とほぼ同じ意味で使われると理解して問題ありません。
初心者がつまずきやすい点
配列名は普通のポインタ変数ではない
numbers は多くの場合ポインタのように使えますが、int *p と完全に同じものではありません。
たとえば次のような代入はできません。
numbers = numbers + 1;配列名そのものを書き換えることはできません。
一方で、ポインタ変数なら次のように書けます。
int *p = numbers;
p = p + 1;numbers + 1 は1バイト先ではない
numbers + 1 は、次の要素を指します。int 配列なら int 1個分だけ進みます。
そのため、「アドレスに 1 を足すと1バイト進む」と考えないようにします。
範囲外アクセスは危険
次のようなコードは危険です。
int numbers[3] = {10, 20, 30};
printf("%d\n", numbers[3]);有効な添字は 0 から 2 までです。numbers[3] は範囲外アクセスであり、未定義動作になります。
同じことはポインタでも起こります。
printf("%d\n", *(numbers + 3));これも安全ではありません。
よくあるエラー
warning: incompatible pointer types
原因: 配列やポインタの型が一致していません。
対処: int 配列なら int * で受け取るようにします。
例:
int numbers[3] = {10, 20, 30};
int *p = numbers;実行結果がおかしい、または異常終了する
原因: 範囲外アクセスをしている可能性があります。
対処: 配列の要素数を確認し、0 から size - 1 の範囲だけを使います。
関数の中で要素数が分からない
原因: 配列を関数に渡すと、先頭要素を指す情報として扱われるためです。
対処: 配列と一緒に要素数も引数で渡します。
練習用コード
添字とポインタの両方で表示する
#include <stdio.h>
int main(void) {
int values[5] = {2, 4, 6, 8, 10};
int i;
for (i = 0; i < 5; i++) {
printf("%d %d\n", values[i], *(values + i));
}
return 0;
}配列の合計を求める
#include <stdio.h>
int sum_array(int *arr, int size) {
int i;
int sum = 0;
for (i = 0; i < size; i++) {
sum += arr[i];
}
return sum;
}
int main(void) {
int values[4] = {5, 10, 15, 20};
printf("%d\n", sum_array(values, 4));
return 0;
}実行結果:
50まとめ
今回のポイントは次のとおりです。
- 配列名は多くの場合、先頭要素のアドレスとして使われる
array[i]と*(array + i)は同じ要素を表す- ポインタ計算は要素の型単位で進む
- 配列を関数に渡すときは、先頭要素を指す形で渡される
- 関数の中では要素数が自動で分からないため、別に渡す必要がある
- 範囲外アクセスは未定義動作であり、行ってはいけない
この回では、配列とポインタが強く結びついていることを確認しました。
配列の添字アクセスは、内部ではポインタの考え方とつながっています。
次回予告
次は、文字列処理の基本を学びます。
文字列は char 配列として扱うため、今回の内容を理解していると strlen や strcpy の動きも整理しやすくなります。

