ポインタの基本(* / &)
| 対応: | C89(1989) |
|---|
ポインタは「変数のメモリアドレス(番地)を格納する変数」です。『&』演算子でアドレスを取得し、『*』演算子でそのアドレスが指す値を読み書きします。ポインタはC言語の核心的な機能であり、配列・文字列・動的メモリ・関数ポインタのすべてに関わります。
構文
// ポインタ変数を宣言する(型へのポインタ) 型 *ポインタ変数名; // 変数のアドレスを取得する(アドレス演算子) &変数名 // ポインタが指す場所の値を取得・変更する(間接参照演算子) *ポインタ変数名 // ポインタの宣言と初期化を同時に行う 型 *ポインタ変数名 = &変数名; // ヌルポインタ(どこも指さない状態を表す) 型 *ポインタ変数名 = NULL;
ポインタ演算子一覧
| 演算子 | 名称 | 概要 |
|---|---|---|
| & | アドレス演算子 | 変数のメモリアドレスを返します。ポインタ変数に代入したり、関数に渡したりするために使います。 |
| *(宣言時) | ポインタ宣言 | 変数がポインタであることを示します。型の後ろに付けます。 |
| *(式中) | 間接参照演算子 | ポインタが指すアドレスにある値を読み書きします。デリファレンスとも呼ばれます。 |
| NULL | ヌルポインタ定数 | どのアドレスも指さないことを表します。ポインタの初期化や無効状態の表現に使います。 |
サンプルコード
『double_value()』はポインタで呼び出し元の変数を変更する関数です。『swap()』は2つの変数の値を交換する関数です。
pointer_basic.c
#include <stdio.h>
void double_value(int *p) {
*p = *p * 2; // ポインタが指す場所の値を2倍にする
}
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main(void) {
int x = 10;
int *p = &x; // x のアドレスを p に格納
printf("x の値: %d\n", x); // 『10』と出力される
printf("x のアドレス: %p\n", (void *)&x);
printf("p の値(アドレス): %p\n", (void *)p);
printf("p が指す値: %d\n", *p); // 『10』と出力される
// ポインタを通じて値を変更する
*p = 20;
printf("変更後の x: %d\n", x); // 『20』と出力される
// 関数にポインタを渡して値を変更する
double_value(&x);
printf("2倍後の x: %d\n", x); // 『40』と出力される
// 値の交換
int a = 5, b = 9;
swap(&a, &b);
printf("a = %d, b = %d\n", a, b); // 『a = 9, b = 5』と出力される
// NULL チェックの例
int *q = NULL;
if (q == NULL) {
printf("ポインタは NULL です。\n");
}
return 0;
}
gcc pointer_basic.c -o pointer_basic ./pointer_basic x の値: 10 x のアドレス: 0x... p の値(アドレス): 0x... p が指す値: 10 変更後の x: 20 2倍後の x: 40 a = 9, b = 5 ポインタは NULL です。
ポインタと配列の関係
C言語では配列名はその先頭要素へのポインタと同等に扱われます。ポインタと配列は表記こそ異なりますが、内部的には同じアドレス演算で動作します。
pointer_array_demo.c
#include <stdio.h>
int main(void) {
int scores[5] = {85, 92, 78, 95, 88};
int *p = scores; // scores は &scores[0] と同じ
// ポインタと配列は同じ要素を指す
printf("scores[2] = %d\n", scores[2]); // 78
printf("*(p + 2) = %d\n", *(p + 2)); // 78(ポインタ演算)
printf("p[2] = %d\n", p[2]); // 78(配列構文)
// ポインタで配列をループ処理する
for (int i = 0; i < 5; i++) {
printf("%d ", *(p + i)); // 85 92 78 95 88
}
printf("\n");
// 文字列もポインタで扱える
const char *name = "草薙京";
printf("%s\n", name); // 草薙京
return 0;
}
gcc pointer_array_demo.c -o pointer_array_demo ./pointer_array_demo scores[2] = 78 *(p + 2) = 78 p[2] = 78 85 92 78 95 88 草薙京
ポインタと配列の詳細は『ポインタと配列』を参照してください。
ダブルポインタ(ポインタへのポインタ)
ダブルポインタ(二重間接参照)はポインタ変数のアドレスを保持します。関数からポインタを書き換えたい場合(動的メモリ確保を関数内で行うなど)に使われます。
double_pointer.c
#include <stdio.h>
#include <stdlib.h>
// 呼び出し元のポインタを初期化する関数
void init_array(int **pp, int size) {
*pp = malloc(sizeof(int) * size);
if (*pp == NULL) return;
for (int i = 0; i < size; i++) {
(*pp)[i] = (i + 1) * 10;
}
}
int main(void) {
int *arr = NULL;
init_array(&arr, 5); // arr のアドレスを渡して、関数内で arr を初期化
if (arr == NULL) { return 1; }
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]); // 10 20 30 40 50
}
printf("\n");
free(arr);
arr = NULL;
return 0;
}
gcc double_pointer.c -o double_pointer ./double_pointer 10 20 30 40 50
よくあるミス1: 未初期化ポインタの参照
初期化していないポインタを参照すると未定義動作(実行時クラッシュ)になります。ポインタは宣言時に必ずNULLで初期化してください。
uninitialized_ptr_ng.c
// NG: 初期化していないポインタを参照する(未定義動作) int *p; *p = 42; // クラッシュする
gcc -o uninitialized_ptr_ng uninitialized_ptr_ng.c ./uninitialized_ptr_ng Segmentation fault (core dumped)
// OK: 必ずNULLで初期化してから使う
int *p = NULL;
if (p != NULL) {
*p = 42; // NULLチェックしてから使う
}
よくあるミス2: ダングリングポインタ
解放済みのポインタを使う(ダングリングポインタ)と未定義動作を引き起こします。解放後は必ずNULLを代入してください。
// NG: 解放済みのポインタを使う(ダングリングポインタ) int *q = malloc(sizeof(int)); free(q); *q = 99; // 解放済みなので未定義動作
// OK: 解放後にNULLを代入する free(q); q = NULL; // ダングリングポインタを防ぐ
概要
C言語では関数の引数は値渡し(コピー)が基本です。関数内で変数の値を変更したい場合はポインタを渡します。ポインタを受け取った関数は、そのアドレスに書き込むことで呼び出し元の変数を変更できます。
初期化していないポインタや解放済みのメモリを指すポインタを参照(デリファレンス)すると、クラッシュや予測不能な動作を引き起こします。ポインタを宣言したら初期化し、使い終わったら『NULL』を代入することでバグを防げます。
ポインタと配列の関係については『ポインタと配列』を、動的メモリの確保・解放については『malloc() / free()』を参照してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。