C言語を学ぶ上で、誰もが一度は「難しい」と感じる概念、それが**「ポインタ」**です。しかし、ポインタを理解することで、C言語の真の力を引き出し、より高度で効率的なプログラミングが可能になります。
この記事では、そんなポインタの基本的な考え方から、配列や文字列との関係まで、図や具体的なコードを交えながら、初心者の方にも分かりやすく解説します。
ポインタとは? 〜 メモリアドレスを格納する変数
コンピュータのメモリは、データを保管するための広大なロッカーのようなものです。そして、各ロッカーには「0x1000」のようなユニークな**住所(アドレス)**が割り振られています。
私たちが int value = 10;
のように変数を宣言すると、コンピュータは空いているロッカーの一つを確保し、そこに10
という値を格納します。
ポインタとは、この**「変数が格納されているメモリアドレス」**そのものを値として保持するための、特殊な変数です。
ポインタの基本的な使い方
ポインタを扱うには、2つの重要な演算子を覚える必要があります。
&
(アドレス演算子): 変数の前に付けると、その変数のメモリアドレスを取得します。*
(間接参照演算子): ポインタ変数の前に付けると、そのポインタが指し示しているアドレスに格納されている値を取得します。
サンプルコードで理解する
言葉だけでは難しいので、実際のコードを見ていきましょう。
#include <stdio.h>
int main(void) {
int value = 100; // 普通の整数型変数
int *ptr; // int型の値を指すためのポインタ変数
// 1. value変数のアドレスを、ptrに代入する
ptr = &value;
printf("--- 変数 value の情報 ---\n");
printf("値: %d\n", value);
printf("アドレス: %p\n", (void *)&value); // %pはアドレスを表示する書式指定子
printf("\n--- ポインタ ptr の情報 ---\n");
printf("値 (valueのアドレス): %p\n", (void *)ptr);
printf("ptr自身のアドレス: %p\n", (void *)&ptr);
printf("ptrが指す先の値 (*ptr): %d\n", *ptr);
// ポインタ経由で元の変数の値を変更する
*ptr = 250;
printf("\n*ptr を使って値を変更した後の value の値: %d\n", value);
return 0;
}
実行結果(アドレスは環境により異なります):
--- 変数 value の情報 ---
値: 100
アドレス: 0x7ffc1e3a4b54
--- ポインタ ptr の情報 ---
値 (valueのアドレス): 0x7ffc1e3a4b54
ptr自身のアドレス: 0x7ffc1e3a4b58
ptrが指す先の値 (*ptr): 100
*ptr を使って値を変更した後の value の値: 250
この結果から、ptr
が value
のアドレスを保持していること、そして *ptr
を使って value
の値を読み書きできることが分かります。
NULLポインタ
どこも指し示していない、無効なポインタを明示するために NULL
という特別な値があります。ポインタ変数は、まだ指し示す先が決まっていない場合、NULL
で初期化しておくのが安全な作法です。 int *safe_ptr = NULL;
配列とポインタの密接な関係
C言語では、配列とポインタは非常に密接な関係にあります。実は、配列名はその配列の先頭要素を指すポインタとして扱うことができます。
つまり、data
という配列があるとき、data
は &data[0]
と同じ意味になります。
この性質を利用すると、配列の各要素には2通りの方法でアクセスできます。
- 配列形式:
data[i]
- ポインタ形式:
*(data + i)
(data
のアドレスからi
個分進んだ先、という意味)
サンプルコード:
#include <stdio.h>
int main(void) {
int scores[] = {85, 92, 78};
printf("--- 配列 scores の各要素へのアクセス ---\n");
for (int i = 0; i < 3; i++) {
printf("要素[%d]:\n", i);
// 同じ値とアドレスが表示されることを確認
printf(" 配列形式の値: %d, アドレス: %p\n", scores[i], (void *)&scores[i]);
printf(" ポインタ形式の値: %d, アドレス: %p\n", *(scores + i), (void *)(scores + i));
}
return 0;
}
実行結果:
--- 配列 scores の各要素へのアクセス ---
要素[0]:
配列形式の値: 85, アドレス: 0x7ffc...b44
ポインタ形式の値: 85, アドレス: 0x7ffc...b44
要素[1]:
配列形式の値: 92, アドレス: 0x7ffc...b48
ポインタ形式の値: 92, アドレス: 0x7ffc...b48
要素[2]:
配列形式の値: 78, アドレス: 0x7ffc...b4c
ポインタ形式の値: 78, アドレス: 0x7ffc...b4c
結果が一致することからも、配列とポインタが内部的に深く結びついていることがわかります。
文字列とポインタ
C言語では、char *
型のポインタを使って文字列を扱うことが非常によくあります。
#include <stdio.h>
int main(void) {
char *message = "Hello, Pointer!";
printf("%s\n", message);
return 0;
}
このコードは、メモリ上に確保された "Hello, Pointer!"
という文字列データの先頭アドレスを、ポインタ変数 message
が指し示している状態です。printf
の %s
は、指定されたアドレスからNULL文字(文字列の終端)までを文字として表示するため、このようにして文字列全体を出力できます。