ねらい:「ポインタ=アドレスを扱う仕組み」を、短い実行例で直感 → 理解へ。
p は「x のある場所(アドレス)」を持っています。
*p と書くと「p が指す先の中身」= x の値 42 が得られます。
補足:変数名はコンパイル時の"ラベル"。実行時にはアドレスで参照される。
| 記号・書き方 | 意味 | 例 |
|---|---|---|
&(アドレス演算子) | 変数のアドレスを得る | &x → x のアドレス |
*(間接参照演算子) | ポインタが指す先の中身を読む/書く | *p → p が指す先の値 |
int *p;(宣言) | 「int を指すポインタ変数 p」を宣言 | 型により「指す先のデータサイズ」が決まる |
int *p; の * は「ポインタ型を作る記号」。
式 *p = 20; の * は「p が指す先を操作する(間接参照)記号」。
同じ記号でも役割が異なります。
int *p; /* int を指すポインタ変数 p */int* p; と書いても同義です(空白の位置はどちらでも可)。
int x;
int *p;
x = 10;
p = &x; /* p に「x のアドレス」を代入 → p は「x のある場所」を指す */*p = 20; /* x が 20 に書き換わる */
printf("%d\n", *p); /* 20 と表示される */*p は「p が指す先の中身」。読みも書きもできます。
ファイル名: 30_ptr_01_intro_basic.c
#include <stdio.h>
int main(void) {
int x; /* 通常の int 変数 */
int *p; /* int を指すポインタ */
x = 42; /* 通常の代入 */
p = &x; /* p に x のアドレスを代入 */
printf("x = %d\n", x); /* x の中身 */
printf("&x = %p\n", (void*)&x); /* x のアドレス(%p は (void*) でキャスト)*/
printf("p = %p\n", (void*)p); /* p の中身(= &x と同じはず)*/
printf("*p = %d\n", *p); /* p が指す先の中身(= x の値)*/
*p = 99; /* 間接参照で x を書き換える */
printf("x = %d\n", x); /* → 99 に変わる */
return 0;
}
実行例(アドレスの数値は実行環境により異なる)
x = 42
&x = 0000007CDB4FF794
p = 0000007CDB4FF794
*p = 42
x = 99
| 式 | 意味 | 実行例の値 |
|---|---|---|
x | x の中身 | 42 → 99 |
&x | x のアドレス | 0x...794 |
p | p が持つアドレス(= &x) | 0x...794(同上) |
*p | p が指す先の中身(= x の値) | 42 → 99 |
&x と p の数値が一致していることを確認しましょう。
p は「矢印の出発点(アドレス)」、*p は「矢印の先の箱の中身」です。
*p = 99; で書き換えると x の値も変わります(同じ場所を指しているため)。
ファイル名: 30_ptr_01_init_and_null.c
#include <stdio.h>
int main(void) {
int a;
int *p;
a = 10;
p = &a; /* 有効なアドレスを必ず入れる */
if (p != NULL) {
printf("*p=%d\n", *p);
}
return 0;
}
int *p; *p = 1; のように、どこも指していないポインタを使うと未定義動作です。
プログラムがクラッシュしたり、別の変数を破壊したりします。= &変数」または「= NULL」で初期化してから使いましょう。
| 誤解 | 正しい理解 |
|---|---|
int *p; の * は間接参照 | 宣言時の * は「ポインタ型を作る記号」 |
&x は x の中身 | &x は x のアドレス |
%p はそのまま使える | (void*) にキャストして渡すのが C の正しい書き方 |
| 型が違うポインタで触っても大丈夫 | 型違いの間接参照は未定義動作の温床 |
int x = 5;
int *p = &x;
*p = 8;Q1. 上記コード実行後の x の値は?
ア. 5 イ. 8 ウ. 不定 エ. 実行時エラー
*p = 8; は「p が指す先 = x の中身」を 8 に書き換える。
Q2. 上記コードの正しい説明はどれ?
ア. &x は x の中身 イ. *p は p に格納されたアドレス ウ. p はアドレス、*p はその先の中身 エ. int *p; は「int の値」
&x は x のアドレス。p はそのアドレスを保持し、*p はアドレスが指す先の値。
Q3. 次のうち危険なコードはどれ?
ア. int *p = NULL; イ. int *p; *p = 1; ウ. int x; int *p = &x; エ. if (p != NULL) { ... }
ファイル名: 30_task_ptr_trace.c
int x=1; int *p=&x; *p=2; x=3; *p=4; の各行の後の x と *p を printf で出力し、手でもトレースして一致を確認する。
#include <stdio.h>
int main(void) {
int x;
int *p;
x = 1;
p = NULL;
printf("after x=1 : x=%d, *p=(未設定)\n", x);
p = &x; /* p は x を指す */
printf("after p=&x: x=%d, *p=%d\n", x, *p);
*p = 2; /* p 経由で x を 2 に */
printf("after *p=2: x=%d, *p=%d\n", x, *p);
x = 3; /* 直接 x を 3 に */
printf("after x=3 : x=%d, *p=%d\n", x, *p);
*p = 4; /* p 経由で x を 4 に */
printf("after *p=4: x=%d, *p=%d\n", x, *p);
return 0;
}観察:x と *p は常に同じ値で推移する(p は常に x を指しているため)。
ファイル名: 30_task_ptr_address.c
int a=10, b=20; int *pa=&a, *pb=&b; を用意し、&a, &b, pa, pb を %p で出力。その後 *pa/*pb を入れ替えて値の変化を確認する。
#include <stdio.h>
int main(void) {
int a, b;
int *pa, *pb;
int t;
a = 10; b = 20;
pa = &a; pb = &b;
printf("&a=%p &b=%p\n", (void*)&a, (void*)&b);
printf("pa=%p pb=%p\n", (void*)pa, (void*)pb);
printf("*pa=%d *pb=%d\n", *pa, *pb);
t = *pa; /* a の中身を退避 */
*pa = *pb; /* a に b の中身を代入 */
*pb = t; /* b に退避した a の中身を戻す */
printf("after swap: *pa=%d *pb=%d\n", *pa, *pb);
printf("check vars: a=%d b=%d\n", a, b);
return 0;
}観察:&a/&b と pa/pb の数値が一致。値交換は参照先の中身を入れ替えている(pa・pb 自体が指す先は変わらない)。
ファイル名: 30_task_ptr_nullguard.c
ポインタを NULL 初期化 → 条件分岐で安全に代入 → 使用、の流れを実装する。未初期化使用を防ぐガードをコメントで説明すること。
#include <stdio.h>
int main(void) {
int x;
int *p;
x = 123;
p = NULL; /* まずは NULL 初期化(未設定状態を明示)*/
/* 危険例(やってはいけない):NULL のまま触る
*p = 1; → 未定義動作 → コメントアウトのままにする
*/
if (p == NULL) {
p = &x; /* 有効なアドレスをセット */
}
if (p != NULL) {
printf("before: x=%d, *p=%d\n", x, *p);
*p = 999; /* 安全に書き換え可能 */
printf("after : x=%d, *p=%d\n", x, *p);
}
return 0;
}観察:NULL 初期化 → チェック → 安全に使用の流れを徹底。未初期化ポインタの使用は未定義動作に直結する。