30_ポインタ入門_01_導入と基本

ねらい:「ポインタ=アドレスを扱う仕組み」を、短い実行例で直感 → 理解へ。

導入:ポインタとは

変数 x
42
アドレス: 0x0064
ポインタ p
0x0064
(x のアドレスを格納)

p は「x のある場所(アドレス)」を持っています。 *p と書くと「p が指す先の中身」= x の値 42 が得られます。

補足:変数名はコンパイル時の"ラベル"。実行時にはアドレスで参照される。

用語の最小セット

記号・書き方意味
&(アドレス演算子)変数のアドレスを得る&x → x のアドレス
*(間接参照演算子)ポインタが指す先の中身を読む/書く*p → p が指す先の値
int *p;(宣言)「int を指すポインタ変数 p」を宣言型により「指す先のデータサイズ」が決まる
* の2つの顔: 宣言 int *p;* は「ポインタ型を作る記号」。 式 *p = 20;* は「p が指す先を操作する(間接参照)記号」。 同じ記号でも役割が異なります。

3ステップで理解する

ステップ1:ポインタ変数を宣言する

int *p;   /* int を指すポインタ変数 p */

int* p; と書いても同義です(空白の位置はどちらでも可)。

ステップ2:変数のアドレスをポインタに代入する

int x;
int *p;

x = 10;
p = &x;   /* p に「x のアドレス」を代入 → p は「x のある場所」を指す */

ステップ3:指す先(間接参照)を読む/書く

*p = 20;            /* x が 20 に書き換わる */
printf("%d\n", *p);  /* 20 と表示される */

*p は「p が指す先の中身」。読みも書きもできます。


実行例1:変数・アドレス・間接参照

ファイル名: 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
意味実行例の値
xx の中身42 → 99
&xx のアドレス0x...794
pp が持つアドレス(= &x)0x...794(同上)
*pp が指す先の中身(= x の値)42 → 99
観察ポイント: &xp の数値が一致していることを確認しましょう。 p は「矢印の出発点(アドレス)」、*p は「矢印の先の箱の中身」です。 *p = 99; で書き換えると x の値も変わります(同じ場所を指しているため)。

実行例2:安全な初期化と NULL

ファイル名: 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  ウ. 不定  エ. 実行時エラー

解説 【正解】イ(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) { ... }

解説 【正解】イ。未初期化のポインタはどこを指すか不明で、書き込みは未定義動作(クラッシュや別変数の破壊の原因)。

自作課題

課題1:値の書き換えトレース

ファイル名: 30_task_ptr_trace.c

int x=1; int *p=&x; *p=2; x=3; *p=4; の各行の後の x*pprintf で出力し、手でもトレースして一致を確認する。

コード例
#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 を指しているため)。

課題2:アドレスの観察

ファイル名: 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/&bpa/pb の数値が一致。値交換は参照先の中身を入れ替えている(pa・pb 自体が指す先は変わらない)。

課題3:NULL チェックの型

ファイル名: 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 初期化 → チェック → 安全に使用の流れを徹底。未初期化ポインタの使用は未定義動作に直結する。