C言語を学んでいると、「ポインタと配列って何が違うの?」「配列名がポインタになるってどういうこと?」と混乱することはありませんか?
実は、ポインタと配列は似ているようで違うものなんです。
でも、この2つの関係を理解すると、C言語の理解が一気に深まります。
今回は、ポインタと配列の関係性、違い、そして実際の使い方まで、初心者でも分かるように丁寧に解説していきます。
ポインタと配列の基本を復習

まず、それぞれの基本を確認しましょう。
配列とは
配列は、同じ型のデータを連続して並べて格納する仕組みです。
int arr[5] = {10, 20, 30, 40, 50};
この配列は、メモリ上で連続した場所に格納されます。
メモリアドレス 値
0x1000 10 (arr[0])
0x1004 20 (arr[1])
0x1008 30 (arr[2])
0x100C 40 (arr[3])
0x1010 50 (arr[4])
int型は通常4バイトなので、各要素のアドレスは4ずつ増えています。
ポインタとは
ポインタは、変数のアドレス(メモリ上の場所)を格納する変数です。
int *p; // int型へのポインタ変数
ポインタを使うと、変数のアドレスを通じて間接的にその変数にアクセスできます。
int x = 100;
int *p = &x; // xのアドレスをpに代入
printf("%d\n", *p); // 100が出力される(*pでxの値にアクセス)
配列名とポインタの不思議な関係
ここが多くの人が混乱するポイントです。
配列名は先頭要素のアドレスを表す
重要なポイント:配列名は、その配列の先頭要素のアドレスと同じ意味になります。
int arr[5] = {10, 20, 30, 40, 50};
printf("%p\n", arr); // 配列の先頭アドレス
printf("%p\n", &arr[0]); // 先頭要素のアドレス
// この2つは同じ値を表示する!
つまり、arr と &arr[0] は等価なんです。
配列名をポインタに代入できる
配列名がアドレスを表すので、ポインタ変数に代入できます。
int arr[5] = {10, 20, 30, 40, 50};
int *p;
p = arr; // これでOK!pはarrの先頭要素を指す
printf("%d\n", *p); // 10が出力される
これは実質的に p = &arr[0]; と同じ意味です。
ポインタで配列要素にアクセスする方法
ポインタを使って配列の各要素にアクセスできます。
基本的なアクセス方法
#include <stdio.h>
int main(void) {
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // pは配列の先頭を指す
printf("%d\n", *p); // 10 (arr[0]と同じ)
printf("%d\n", *(p + 1)); // 20 (arr[1]と同じ)
printf("%d\n", *(p + 2)); // 30 (arr[2]と同じ)
return 0;
}
重要な等価関係
arr[i]は*(arr + i)と等価&arr[i]はarr + iと等価
ポインタのインクリメント
ポインタを+1すると、次の要素を指すようになります。
#include <stdio.h>
int main(void) {
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;
printf("%d\n", *p); // 10
p++; // 次の要素へ
printf("%d\n", *p); // 20
p++; // さらに次へ
printf("%d\n", *p); // 30
return 0;
}
注意点:型のサイズ分だけ移動する
p++ は単に+1するのではなく、その型のサイズ分だけアドレスが進みます。
- char型ポインタ:+1バイト
- int型ポインタ:+4バイト(通常)
- double型ポインタ:+8バイト(通常)
配列記法とポインタ記法の比較
実は、ポインタ変数でも配列と同じ記法が使えます。
#include <stdio.h>
int main(void) {
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;
// どちらの書き方でもOK
printf("%d\n", arr[2]); // 30
printf("%d\n", p[2]); // 30(ポインタでも[]が使える!)
printf("%d\n", *(arr + 2)); // 30(配列でもポインタ演算が使える!)
printf("%d\n", *(p + 2)); // 30
return 0;
}
すべて同じ結果になります!
配列とポインタの重要な違い
似ているようで、実は大きな違いがあります。
違い1:実体とアドレス
配列
配列を宣言すると、実際にデータを格納する領域が確保されます。
int arr[5]; // 20バイト(4バイト×5)のメモリが確保される
ポインタ
ポインタ変数は、アドレスを格納する変数でしかありません。
int *p; // アドレスを格納する変数(通常4または8バイト)
違い2:変更可能性
これが最も重要な違いです。
配列名は定数ポインタのように振る舞う
配列名自体は変更できません。
int arr[5] = {10, 20, 30, 40, 50};
arr++; // エラー!配列名は変更できない
arr = arr + 1; // エラー!
ポインタ変数は変更できる
ポインタは普通の変数なので、指す先を変更できます。
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;
p++; // OK!pが次の要素を指すようになる
p = arr + 3; // OK!pが4番目の要素を指すようになる
違い3:sizeof演算子の結果
#include <stdio.h>
int main(void) {
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;
printf("sizeof(arr) = %zu\n", sizeof(arr)); // 20(配列全体のサイズ)
printf("sizeof(p) = %zu\n", sizeof(p)); // 4 or 8(ポインタ変数のサイズ)
return 0;
}
配列にsizeofを使うと配列全体のサイズが返りますが、ポインタではポインタ変数自体のサイズしか返りません。
ループでの配列アクセス

配列の全要素を処理する典型的なパターンを見てみましょう。
配列記法を使う方法
#include <stdio.h>
int main(void) {
int arr[5] = {10, 20, 30, 40, 50};
int i;
for (i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
これが最も分かりやすい書き方です。
ポインタ演算を使う方法
#include <stdio.h>
int main(void) {
int arr[5] = {10, 20, 30, 40, 50};
int *p;
for (p = arr; p < arr + 5; p++) {
printf("%d ", *p);
}
printf("\n");
return 0;
}
ポインタをインクリメントしながら要素にアクセスします。
どちらを使うべき?
現代のC言語では、配列記法(arr[i])の使用が推奨されます。
理由:
- コードが読みやすい
- 意図が明確
- 現代のコンパイラは最適化が優秀
ポインタ演算は、昔は高速化のために使われましたが、今ではコンパイラが自動的に最適化してくれます。
関数への配列の渡し方
関数に配列を渡す際、実際にはポインタが渡されます。
配列を引数に渡す
#include <stdio.h>
void printArray(int arr[], int size) {
int i;
for (i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main(void) {
int numbers[5] = {10, 20, 30, 40, 50};
printArray(numbers, 5);
return 0;
}
ポインタで受け取る(等価)
実は、以下の書き方も全く同じ意味です。
void printArray(int *arr, int size) {
// 処理内容は上と全く同じ
}
int arr[] と int *arr は、関数の引数としては完全に同じです。
重要な注意点
関数内でsizeofを使うと、配列全体のサイズは分かりません。
void printArray(int arr[], int size) {
printf("%zu\n", sizeof(arr)); // ポインタのサイズが返る(4 or 8)
// 配列全体のサイズは分からない!
}
だから、配列のサイズは別の引数で渡す必要があります。
文字列とポインタ
文字列は文字の配列なので、ポインタと深く関係します。
文字配列
char str1[10] = "Hello";
これは配列なので、str1自体は変更できません。
文字列リテラルとポインタ
char *str2 = "Hello";
この場合、”Hello”は文字列リテラル(読み取り専用)で、str2はそのアドレスを指します。
違いに注意
char str1[10] = "Hello";
str1[0] = 'h'; // OK(配列の内容を変更)
char *str2 = "Hello";
str2[0] = 'h'; // 未定義動作!文字列リテラルは変更できない
ポインタと配列の実用例
実際のプログラムでどう使うか見てみましょう。
配列の合計を計算
#include <stdio.h>
int sum(int *arr, int size) {
int total = 0;
int i;
for (i = 0; i < size; i++) {
total += arr[i]; // または total += *(arr + i);
}
return total;
}
int main(void) {
int numbers[5] = {10, 20, 30, 40, 50};
int result;
result = sum(numbers, 5);
printf("合計: %d\n", result); // 150
return 0;
}
配列の要素を2倍にする
#include <stdio.h>
void doubleArray(int *arr, int size) {
int i;
for (i = 0; i < size; i++) {
arr[i] *= 2; // 元の配列を直接変更
}
}
int main(void) {
int numbers[5] = {10, 20, 30, 40, 50};
int i;
doubleArray(numbers, 5);
for (i = 0; i < size; i++) {
printf("%d ", numbers[i]); // 20 40 60 80 100
}
return 0;
}
ポインタ(配列)を渡すと、関数内で元の配列を変更できます。
よくある間違いと注意点
初心者がつまづきやすいポイントをまとめます。
間違い1:配列名をインクリメント
int arr[5] = {10, 20, 30, 40, 50};
arr++; // エラー!配列名は変更できない
正しい方法
int *p = arr;
p++; // OK
間違い2:ポインタを初期化せずに使う
int *p;
*p = 100; // 危険!pが何を指しているか不明
正しい方法
int x;
int *p = &x;
*p = 100; // OK
間違い3:関数内でsizeofを使う
void func(int arr[]) {
int size = sizeof(arr) / sizeof(arr[0]); // 間違い!
// arrはポインタなので正しく計算できない
}
正しい方法
void func(int arr[], int size) {
// サイズを引数で受け取る
}
int main(void) {
int numbers[5] = {1, 2, 3, 4, 5};
int size = sizeof(numbers) / sizeof(numbers[0]); // ここで計算
func(numbers, size);
return 0;
}
間違い4:文字列リテラルの変更
char *str = "Hello";
str[0] = 'h'; // 未定義動作!
正しい方法
char str[] = "Hello"; // 配列として宣言
str[0] = 'h'; // OK
まとめ:配列とポインタの使い分け
配列とポインタの関係を理解すると、C言語の理解が深まります。
配列とポインタの関係
- 配列名は先頭要素のアドレスを表す
arr[i]は*(arr + i)と等価- 配列名をポインタに代入できる
- ポインタ変数でも配列記法が使える
重要な違い
| 項目 | 配列 | ポインタ |
|---|---|---|
| 実体 | データを格納する領域を確保 | アドレスを格納する変数 |
| 変更 | 配列名自体は変更不可 | ポインタ変数は変更可能 |
| sizeof | 配列全体のサイズ | ポインタ変数のサイズ |
| 初期化 | 宣言時に領域確保 | 明示的に代入が必要 |
使い分けのポイント
配列を使う場面
- サイズが決まっている場合
- データをまとめて管理したい場合
- 自動的にメモリを確保したい場合
ポインタを使う場面
- 動的にメモリを確保する場合(malloc)
- 関数間でデータを効率的に渡す場合
- データ構造(リスト、ツリー等)を作る場合
コーディングのベストプラクティス
- 配列アクセスには[]を使う(arr[i])が読みやすい
- 配列サイズは別途渡す(sizeof問題を避ける)
- ポインタは必ず初期化する
- 文字列リテラルは変更しない
C言語のポインタと配列は、最初は難しく感じるかもしれません。
でも、「配列名はアドレス」「ポインタは変数」という基本を押さえれば、必ず理解できます。
たくさんコードを書いて、実際に動かしながら学んでいきましょう!


コメント