閉じる

【C言語】ポインタの間接参照と宣言の「*」の違いを初心者向けに徹底解説

プログラミング初心者にとって、C言語のポインタとは最初の大きな壁になりやすいテーマです。

本記事では、ポインタの基本から、宣言に使うと間接参照(デリファレンス)で使うの違いまでを、図解イメージとサンプルコードを交えながら丁寧に解説します。

読むうちに「なぜが2種類あるように見えるのか」がスッキリ理解できることを目指します。

ポインタとは

ポインタとは何かを初心者向けに解説

ポインタ(pointer)とは「変数が置かれている場所(アドレス)を記録するための変数」です。

ふつうの変数は「値」を直接持ちますが、ポインタ変数は「他の変数がメモリ上のどこにあるか」という住所(アドレス)を持ちます。

  • int x = 10;
    → 変数x10というを持ちます。
  • int *p = &x;
    → ポインタ変数pxアドレスを持ちます。

ポインタを使うと、「どの変数を操作するか」をアドレスで指し示すことができるので、柔軟なプログラムが書けるようになります。

簡単なたとえ話

変数を「家」、アドレスを「住所」、ポインタを「住所が書かれた紙」と考えるとイメージしやすくなります。

  • 家(変数本体)…中に実際の値が入っている
  • 住所(アドレス)…その家がどこにあるか
  • 住所が書かれた紙(ポインタ)…どの家に行けばよいかを示す情報

ポインタそのものは値を直接持つわけではなく、「どの値が置かれている場所か」を間接的に指す存在なのです。

アドレスと値の違いを図解イメージで説明

図では、メモリをいくつかの箱として並べ、それぞれにアドレス中身(値)を表示します。

想像してみてください。

  • アドレス0x100の場所にxがいて、その中身は10
  • アドレス0x200の場所にpがいて、その中身は0x100

このとき、

  • xの値」は10
  • xのアドレス」は0x100
  • pの値」は0x100で、「xのアドレスと同じ」

という関係になります。

ポインタ変数pの「値」そのものが、他の変数(x)の「アドレス」になるという点が最重要ポイントです。

ポインタが必要になる典型的な場面

ポインタはC言語で非常に重要な概念で、さまざまな場面で使われます。

代表的なケースをいくつか紹介します。

1つ目は関数から複数の値を返したいときです。

C言語の関数は戻り値を1つしか返せないため、複数の結果を返したいときにポインタを使って呼び出し元の変数を書き換えます。

2つ目は配列や文字列の操作です。

配列名は実質的に先頭要素へのポインタとして扱われるため、配列とポインタは密接に関係しています。

文字列も文字の配列であり、ポインタを介して扱うことが一般的です。

3つ目は動的メモリ確保です。

mallocなどの関数は、ヒープ領域から確保したメモリのアドレスをポインタとして返します。

これにより、実行時に必要なだけメモリを確保することができます。

このように、ポインタは

  • 関数間でのデータ受け渡し
  • 配列・文字列の処理
  • 動的メモリ管理

といった、C言語で避けて通れない重要な役割を担っています。

「*」の使い方1 ポインタ宣言の意味

C言語のポインタ宣言での「*」の役割

ポインタを使うためには、まずポインタ変数を宣言します。

そのときの書き方が次のような形です。

C言語
int *p;   // int型へのポインタ変数pの宣言

この*「ポインタ型であることを示す記号」です。

つまりint *p;は、「int型の値が置かれているアドレスを格納する変数p」という意味になります。

ここでのは『宣言の』であり、まだ値を取り出したりしていません。

pはint型のポインタですよ」とコンパイラに伝えるための印です。

int *p; の正しい読み方とよくある勘違い

int *p;は、初心者の方が特につまずきやすい宣言です。

正しい読み方と、ありがちな勘違いを整理しておきます。

正しいイメージとしては、次のように読みます。

  • pint型へのポインタ
  • pintの場所(アドレス)を格納する変数

よくある誤解としては、

  • *pという名前の変数が宣言された」と考えてしまう
  • *は変数名の一部だ」と思ってしまう

というものがあります。

宣言部分では*変数名につく属性であり、変数名の一部ではありません。

変数名はあくまでpです。

型と「」の位置(int p vs int *p)の違い

C言語では、次の2つの書き方は意味としては同じです。

C言語
int *p;   // よく見かける書き方
int* q;   // こちらも文法的には同じ意味

どちらも「int型へのポインタ変数」を宣言しています。

では、なぜ書き方が分かれるのでしょうか。

スタイルの違いと考え方

  • int p;
    pはintへのポインタ」と、変数名に近い位置にを置いて表現するスタイル
  • int* p;
    「型がintである」と、を型とセットで捉えるスタイル

どちらのスタイルも実際に使われていますが、複数の変数を同時に宣言するときに落とし穴があるので注意が必要です。

この点は次の節で詳しく見ていきます。

複数ポインタ宣言時の注意点と落とし穴

複数の変数を1行で宣言するとき、*の付き方が変数ごとに独立していることに注意しなければなりません。

次のコードを見てください。

C言語
int* p, q;

この宣言は「pもqもintポインタ」ではありません。

実際には、

  • pintへのポインタ(int *)
  • qただのint変数(int)

となります。

これは*各変数名に対して個別に効くからです。

したがって、複数のポインタを1行で宣言したい場合は、次のように書く必要があります。

C言語
int *p, *q;      // pもqもintへのポインタ
double *a, *b;   // aもbもdoubleへのポインタ

初心者のうちは「1行に1つの変数宣言」にしておくと混乱しにくくなります。

C言語
int *p;
int *q;

このように書いておけば、「どれがポインタでどれが通常の変数か」を見間違えにくくなります。

「*」の使い方2 間接参照(デリファレンス)の意味

間接参照演算子「*」で値を取り出す仕組み

次に登場するもう1つの*間接参照(デリファレンス)です。

宣言のときではなく、式の中で

C言語
*p

のように使う*「ポインタが指している先の値を取り出す」という演算子です。

<dl> <dt>pが持っているもの</dt> <dd>「どのアドレスを指しているか」という情報</dd> <dt>*pが表すもの</dt> <dd>「そのアドレスに保存されている値」</dd> </dl>

つまり、宣言と式では*の役割が根本的に違います。

  • 宣言での* … 「これはポインタです」と型を示す記号
  • 式での* … 「ポインタの先の値を取る」という演算子

この違いを意識すると、混乱がかなり減ります。

*p と &x の関係を初心者向けに解説

ポインタとよくセットで登場するのがアドレス演算子&です。

  • &x変数xのアドレスを取り出す
  • *pポインタpが指すアドレスにある値を取り出す

この2つは「行き」と「帰り」のような関係になっています。

具体的な関係をコードで確認してみます。

C言語
#include <stdio.h>

int main(void) {
    int x = 10;      // ふつうのint変数
    int *p = &x;     // xのアドレスをpに代入

    printf("xの値      : %d\n", x);
    printf("&xの値(アドレス): %p\n", (void *)&x);
    printf("pの値(アドレス): %p\n", (void *)p);
    printf("*pの値     : %d\n", *p);

    return 0;
}
実行結果
xの値      : 10
&xの値(アドレス): 0x7ffcf42c5a1c  (例)
pの値(アドレス): 0x7ffcf42c5a1c  (&xと同じ)
*pの値     : 10

p = &x; としたとき、*pxと同じ値を表す」という関係が成り立ちます。

式としては

  • p = &x;
  • *p == x; (ほぼ常に成り立つ)

と覚えると理解しやすくなります。

ポインタ経由で変数を書き換えるコード例

ポインタの大きな利点の1つは、ポインタを通して元の変数の値を変更できることです。

次のサンプルコードで、その動きを確かめてみましょう。

C言語
#include <stdio.h>

int main(void) {
    int x = 10;     // もとの変数
    int *p = &x;    // xのアドレスをpに保存

    printf("変更前: x = %d\n", x);

    // ポインタpを通してxの値を書き換える
    *p = 20;        // 「pが指す先(intの場所)に20を書き込む」

    printf("変更後: x = %d\n", x);
    printf(" *p の値も: %d\n", *p);

    return 0;
}
実行結果
変更前: x = 10
変更後: x = 20
 *p の値も: 20

このコードでは*p = 20;という1行で、xの値が20に書き換えられています。

「ポインタが指す先の変数を、ポインタ経由で変更した」ということです。

関数にポインタを渡して中で*pを書き換えることで、関数の外側にある変数の値を変更することもできます。

間接参照で起きやすいエラー

間接参照*を使うときにもっとも多いのが、「間違ったアドレスを指しているポインタを間接参照してしまう」というエラーです。

代表的なパターンを挙げます。

1つ目は未初期化ポインタの間接参照です。

C言語
int *p;   // どこも指していない(中身はゴミ)
*p = 10;  // 未定義動作(危険)

このケースではpの中身が何かわからない状態で*pを使ってしまっており、どこか不明なメモリを壊す危険があります。

2つ目はNULLポインタの間接参照です。

C言語
int *p = NULL;  // 何も指していないことを明示
*p = 10;        // 絶対にやってはいけない(クラッシュの原因)

3つ目は寿命が切れた変数を指すポインタを使い続けるケースです。

たとえば、関数のローカル変数のアドレスを返してしまうと、関数終了後にその変数は消えてしまうため、ポインタは「行き先のない住所」を指すことになります。

これらのエラーを避けるには、

  • ポインタは必ず有効なアドレスで初期化する
  • NULLにしているときは間接参照しない
  • 指している先の寿命(スコープ)を意識する

といった点を意識することが大切です。

ポインタ宣言の「」と間接参照の「」の違い整理

宣言の「」と式中の「」の役割の違い

ここまで見てきたように、*文脈によって役割がまったく異なります

宣言のときの*:

C言語
int *p;   // 宣言コンテキスト: 「pはint*型」
  • 役割: 「この変数はポインタ型ですよ」と型を指定する
  • 読み方: 「pintへのポインタ」

式の中での*:

C言語
*p = 10;  // 式コンテキスト: 「pが指す先の値」
  • 役割: ポインタが指すアドレスから値を読み書きする
  • 読み方: 「pが指している先の値

同じ*記号でも、「宣言かどうか」で意味が切り替わると覚えてください。

文脈で見分ける「*」の読み方と考え方

*が出てきたら、次の順番で考えると混乱しにくくなります。

  1. その*宣言の中にあるか
    → 型名(intdouble)と一緒に書かれているかをチェックします。
  2. 宣言の中にあるなら
    → 「この変数はポインタ型」という意味だと読む
  3. 式や文の中にあるなら
    → 「このポインタが指す先の値」という意味だと読む

このように「今、自分は宣言を読んでいるのか、式を読んでいるのか」を意識すると、*の意味が自然と決まってきます。

サンプルコードで比較するポインタ宣言と間接参照

宣言のと式中のが混在する、少し長めのサンプルを見てみましょう。

C言語
#include <stdio.h>

// 関数プロトタイプ宣言
void set_value(int *p);

int main(void) {
    int x = 0;      // ふつうのint変数
    int *p = &x;    // 宣言: pはintへのポインタ

    printf("main開始時: x = %d\n", x);

    // 間接参照でxを書き換え
    *p = 10;        // 式: pが指す先の値に10を書き込む
    printf(" *p = 10後: x = %d\n", x);

    // 関数にポインタを渡す
    set_value(p);   // xのアドレスを関数に渡す
    printf("set_value後: x = %d\n", x);

    return 0;
}

// 関数定義
void set_value(int *p) {  // 宣言: pはintへのポインタ
    *p = 20;              // 式: pが指す先の値に20を書き込む
}
実行結果
main開始時: x = 0
 *p = 10後: x = 10
set_value後: x = 20

このコードの中で*がどのように使われているかを整理すると、次のようになります。

  • int p = &x;
    宣言: 「pはintへのポインタ」
  • void set_value(int p);
    宣言: 「引数pはintへのポインタ」
  • p = 10;p = 20;*
    : 「pが指す先の値を書き換える」

このように、同じ*でも、どこに書かれているか(宣言か式か)で意味が変わることが分かります。

初心者が混乱しないための覚え方と練習方法

最後に、初心者の方が*に慣れるための考え方と練習方法を紹介します。

覚え方のコツとしては、次のように整理するとスッキリします。

  • 宣言に出てくる*
    「ポインタ<small>(型)</small>ですよマーク」
    例:int *p; → 「pはintへのポインタ」
  • 式に出てくる
    「指している先の値だよマーク」
    例:p = 10; → 「pが指す先の値に10を代入」

「宣言なら型、式なら値」と1文でまとめて覚えておくのも有効です。

練習方法の例

練習としては、次のような小さなプログラムをいくつか自分で書いてみるのが効果的です。

  1. int x = 5;を宣言し、int *p = &x;と書く
  2. printfx&xp*pをすべて表示してみる
  3. *pを何度か書き換えて、そのたびにxの値がどう変わるか確認する
  4. 関数にポインタを渡し、関数の中で*pを書き換えるコードを書いてみる

このとき、「いま書いているは、宣言のか、式の*か」を意識しながら書くと、自然と区別できるようになっていきます。

まとめ

本記事では、C言語のポインタとについて、「ポインタ宣言における」と「間接参照(デリファレンス)としての」の2つの役割を中心に解説しました。

宣言の「これはポインタ型ですよ」という印であり、式中の「ポインタが指す先の値を扱う演算子」です。

この2つを「宣言なら型、式なら値」と意識し、&との関係(p = &x;ならp == x)を手を動かして確かめていけば、ポインタは必ず理解できます。

小さなサンプルから少しずつ慣れていきましょう。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!