C言語基礎|間接演算子 *

ポインタの“魔法”はこれ! 「*」を付けると「住所の先の本人」に触れられる。

ポインタは「住所(アドレス)を入れる変数」でしたよね。
でも、住所を持っているだけでは、まだ便利さが実感しにくいです。

そこで登場するのが 間接演算子 (単項
これは一言でいうと、

ポインタが指している先のオブジェクトそのものを扱えるようにするスイッチ

です。

  • * を付けると、住所の先にある「本人(値が入っているオブジェクト)」にアクセスできます。
    読み取りもできるし、代入して書き換えることもできます。
    この“住所→本人”の切り替えが分かると、ポインタが一気に気持ちよくなりますよ。

間接演算子 * とは(指している先を扱う)

単項 * 演算子(間接演算子)

書き方意味
*aa が指すオブジェクトそのもの

ここで a は「ポインタ」です。
つまり *a は「そのポインタが指している先(参照先)」になります。

「*p は n のエイリアス」ってどういうこと?

ポインタ p が変数 n を指しているとき、式 *p は n と同じ場所を表します。

  • n … 本名
  • *p … あだ名(別名)

という感じで、同じ本人を違う名前で呼んでいるイメージです。これを エイリアス(alias) と呼びます。

ポインタとエイリアスのイメージ

n  : [ 57 ]      (n は値を持つ箱)
p  : [ &n ] ---> (p は n の住所を持つ)

*p は「矢印の先の箱」= n そのもの

「参照外し(dereference)」= *で先を開ける操作

ポインタに * を付けて、指している先にアクセスすることを 参照外し と呼びます。

  • *p で「読む」:指している先の値を読む
  • *p で「書く」:指している先の値を書き換える

*p ができること

書き方意味
x = *p;指す先の値を読むpがnを指すなら、xにnの値が入る
*p = 999;指す先に書くpがnを指すなら、nが999になる

超重要:指していないポインタを * しない

ポインタが「どこも指していない」状態で参照外しをすると、結果は予測不能になりがちです(クラッシュや変な値など)。

安全な考え方

状態*してよい?
正しく初期化済みint *p = &n;OK
代入で指す先が確定p = &x;OK
未初期化int *p;だめ
変な値が入っているp = (int *)1234;だめ(特別な理由がない限り)

「*を使う前に、必ず指す先を決める」これが鉄則です。

サンプルプログラム

温度(temp)か湿度(hum)どちらを更新するか選ぶプログラム例です。

プロジェクト名:chap10-4-1 ソースファイル名:chap10-4-1.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

#include <stdio.h>

int main(void)
{
    int temp = 22;   // 温度(例)
    int hum  = 55;   // 湿度(例)
    int sel;

    puts("現在の値を表示します。");
    printf("temp = %d\n", temp);
    printf("hum  = %d\n", hum);

    printf("どちらを更新しますか? 0:temp  1:hum  = ");
    scanf("%d", &sel);

    int *p;
    if (sel == 0) {
        p = &temp;   // pはtempを指す
    } else {
        p = &hum;    // pはhumを指す
    }

    *p = 99;         // 指している先を更新

    puts("更新後の値です。");
    printf("temp = %d\n", temp);
    printf("hum  = %d\n", hum);

    return 0;
}

実行イメージ(例)

  • 入力が0なら temp が 99 になる
  • 入力が1なら hum が 99 になる

同じ *p = 99; なのに、代入先が実行時に変わるのがポイントです。

このプログラムで起きていること(図で分解)

図A:sel が 0 のとき(p は temp を指す)

p = &temp
*p = 99

temp : [ 99 ]  <--- p
hum  : [ 55 ]

図B:sel が 1 のとき(p は hum を指す)

p = &hum
*p = 99

temp : [ 22 ]
hum  : [ 99 ]  <--- p

図の説明(読み方)

  • 箱はオブジェクト(tempやhum)
  • p は「住所」を持ち、矢印で指す先が決まる
  • *p は「矢印の先の箱そのもの」なので、*p = 99 はその箱の中身を書き換える

「静的」と「動的」(代入先がいつ決まる?)

この例の面白さは、*p = 99; の行だけ見ても、代入先が temp なのか hum なのか分からないところです。

  • コンパイル時には「どっちに書くか」は固定されていない。
  • 実行時に sel の値で p の指す先が決まる。

これを「アクセス先が実行時に動的に決まる」と説明できます。

静的・動的のイメージ

用語意味この例でいうと
静的(static)時間が経っても変わらないtemp = 99; のように書けば代入先は固定
動的(dynamic)実行時の状況で変わる*p = 99; は p次第で代入先が変わる

& と * の評価(何が得られる?)

ここは混乱しやすいので、値の種類まで整理します。
temp を指しているときで考えます。

式の評価結果

評価して得られるものたとえば
tempint の値22 や 99
&tempint へのポインタ(住所)0x7ffd...
pint へのポインタ(住所)&temp と同じ値
*pint の値(指している先の値)temp の中身

ポインタ宣言の落とし穴(* は変数ごとに付ける)

次の宣言はよく勘違いされます。

int *p1, p2;

これは

  • p1 は int へのポインタ
  • p2 は int(ただの整数)

になります。

正しく書く例

目的書き方
ポインタを2つint *p1, *p2;
分けて明確にint *p1; int *p2;

読みやすさ重視なら分けて書くのも全然アリです。

登場する命令(関数)と書式・何をする?

この回で出てきた命令(関数)は puts, printf, scanf です。

puts

  • 書式:puts(文字列);
  • 何をする?:文字列を表示して改行も出します

printf

  • 書式:printf(書式文字列, 引数1, 引数2, ...);
  • 何をする?:書式に合わせて値を表示します

scanf

  • 書式:scanf(書式文字列, 変数のアドレス, ...);
  • 何をする?:入力を読み取り、指定した変数に格納します
  • ポイント:sel に入れるには、scanf("%d", &sel); のように & が必要です(住所を渡して書き込ませるため)

使った表や図の説明(まとめ)

  • 「*演算子の表」は、*p が「指す先そのもの」を表すことを短く定義しています。
  • 「図A/図B」は、p の指す先が変わると *p の代入先も変わることを視覚化しています。
  • 「評価の表」は、temp と &temp と p と *p がそれぞれ何の値になるか(値か住所か)を整理しています。
  • 「宣言の表」は、* の付け忘れでポインタにならない落とし穴を回避するためのまとめです。