[C言語のはじめ方] Part11:アドレス演算子(&)と間接参照(*)

C言語

ポインタへの第一歩を踏み出そう! 🐾 C言語の核心に触れる旅へようこそ。

はじめに:ポインタって何だろう?

C言語の学習を進めていく中で、「ポインタ」という言葉を聞いたことがあるかもしれませんね。ポインタはC言語の非常に強力な機能の一つですが、同時に多くの初学者が「難しい… 🤔」と感じるポイントでもあります。でも、安心してください!この Step 3 では、ポインタの基本から一つずつ丁寧に解説していきます。

今回は、その第一歩としてアドレス演算子 &間接参照演算子 * を学びます。これらはポインタを扱う上で絶対に欠かせない、基本的な「道具」のようなものです。この記事を読み終える頃には、ポインタの基本的な考え方と、これらの演算子の使い方がしっかりと理解できているはずです。さあ、一緒にポインタの世界を探検しましょう!🚀

メモリとアドレス:コンピュータの記憶の仕組み 🧠

まず、ポインタを理解するための前提知識として、コンピュータがどのようにデータを記憶しているか、その基本的な仕組みについて触れておきましょう。

コンピュータは、プログラムやデータをメモリと呼ばれる記憶装置に保存します。このメモリは、たくさんの小さな「部屋」や「箱」がずらっと並んでいるようなものだとイメージしてください。データを保存するということは、これらの部屋のどれかにデータを入れるということです。

💡 例え話:メモリは巨大なロッカー室

コンピュータのメモリを、たくさんのロッカーが並んだ巨大なロッカー室に例えてみましょう。
荷物(データ)を預けたいとき、空いているロッカーを選んで荷物を入れますよね。
そして、どのロッカーに何を入れたか忘れないように、ロッカーには番号が付いています(例:「101号室」「205号室」)。
このロッカー番号に相当するのが、メモリの世界でのアドレスです。

C言語で変数を宣言すると(例: int age = 25;)、コンピュータはその変数 age のためのメモリ領域(ロッカー)を確保し、そこに値 25 を格納します。そして、そのメモリ領域(ロッカー)には、固有のアドレス(ロッカー番号)が割り当てられます。

ポインタは、このアドレスを扱うための仕組みなのです。つまり、データそのものではなく、「データがメモリ上のどこにあるか」という場所の情報(住所)を扱うのがポインタの役割です。

アドレス演算子 &:変数の「住所」を調べる 🗺️

それでは、具体的に変数のアドレス(住所)を知る方法を見ていきましょう。そのために使うのがアドレス演算子 & です。アンパサンド(ampersand)と読みます。

アドレス演算子 & は、変数の前に付けることで、その変数が格納されているメモリ上のアドレスを取得することができます。「〜のアドレス」と読むと分かりやすいでしょう。

実際の使い方をコードで見てみましょう。

// アドレス演算子の使用例
#include <stdio.h>

int main(void) {
    int score = 85; // int型の変数scoreを宣言し、85で初期化

    // 変数scoreの値を表示
    printf("変数scoreの値: %d\n", score);

    // アドレス演算子 & を使って、変数scoreのメモリアドレスを表示
    printf("変数scoreのアドレス: %p\n", &score); // &score で scoreのアドレスを取得

    return 0;
}
      

このコードを実行すると、例えば次のような出力が得られます(アドレスの値は実行環境によって異なります)。

変数scoreの値: 85
変数scoreのアドレス: 0x7ffeea2b9abc
      

printf 関数でアドレスを表示する際には、%p という書式指定子を使います。これは「ポインタ(アドレス)」を表示するためのものです。通常、アドレスは16進数で表示されます(先頭の 0x は16進数であることを示します)。

このように、アドレス演算子 & を使うことで、どんな変数でも、それがメモリ上のどこに存在しているのか(住所)を知ることができるのです。簡単ですね!😊

ポインタ変数:アドレスをしまうための特別な変数 📥

さて、アドレス演算子 & で変数のアドレスを取得できることがわかりました。では、取得したアドレスをどこかに保存しておきたくなるのが人情ですよね? 😉 そのために用意されているのがポインタ変数です。

ポインタ変数とは、その名の通り、メモリアドレスを値として格納することができる特別な変数です。普通の変数(例えば int 型の変数)が数値などを格納するのに対し、ポインタ変数は「場所の情報(アドレス)」を格納します。

ポインタ変数を宣言するには、データ型の後にアスタリスク * を付け、その後に変数名を記述します。

データ型 *ポインタ変数名;

例えば、int 型の変数のアドレスを格納するためのポインタ変数は、次のように宣言します。

int *ptr; // int型のデータへのポインタ変数 ptr を宣言

これは「ptr は、int 型のデータが存在するメモリアドレスを格納するための変数ですよ」という意味になります。* は、この変数がポインタ変数であることを示しています。

では、実際にアドレス演算子 & とポインタ変数を使ってみましょう。変数 num のアドレスをポインタ変数 ptr に代入する例です。

// ポインタ変数にアドレスを代入する例
#include <stdio.h>

int main(void) {
    int num = 100;    // int型の変数 num
    int *p_num;       // int型へのポインタ変数 p_num (pointer to num のつもり)

    // 変数numのアドレスをアドレス演算子(&)で取得し、
    // ポインタ変数p_numに代入する
    p_num = &num;

    printf("変数numの値: %d\n", num);
    printf("変数numのアドレス (&num): %p\n", &num);
    printf("ポインタ変数p_numの値 (格納しているアドレス): %p\n", p_num);

    // ポインタ変数p_num自体も変数なので、メモリ上にアドレスを持つ
    printf("ポインタ変数p_numのアドレス (&p_num): %p\n", &p_num);

    return 0;
}
      

実行結果の例です(アドレスは環境依存)。

変数numの値: 100
変数numのアドレス (&num): 0x7ffee1f3dabc
ポインタ変数p_numの値 (格納しているアドレス): 0x7ffee1f3dabc
ポインタ変数p_numのアドレス (&p_num): 0x7ffee1f3dab0
      

注目してほしいのは、&num の値と p_num の値が同じになっている点です。これは、p_numnum のアドレスが正しく格納されたことを示しています。

また、最後の行ではポインタ変数 p_num 自身の アドレス (&p_num) を表示しています。ポインタ変数も変数の一種なので、メモリ上のどこかに格納されており、自身のアドレスを持っているということも覚えておきましょう。

重要: ポインタ変数を宣言する際は、「どの型のデータへのアドレスを格納するか」を明確に指定する必要があります。

  • int *p_int;int 型データのアドレス用。
  • float *p_float;float 型データのアドレス用。
  • char *p_char;char 型データのアドレス用。

原則として、指し示す先のデータの型とポインタ変数の型は一致させる必要があります。これは、後述する間接参照演算子を使う際に、メモリ上のどこからどこまでをデータとして読み取るかを正しく判断するために不可欠です。

間接参照演算子 *:アドレスの先の「中身」を見る・変える 🎯

ポインタ変数にアドレスを格納できるようになったところで、次はそのアドレスが指し示している「先」にある実際のデータにアクセスする方法を学びましょう。そのために使うのが、間接参照演算子 * です。アスタリスク(asterisk)と読みます。デリファレンス(dereference)演算子と呼ばれることもあります。

間接参照演算子 * は、ポインタ変数の前に付けることで、そのポインタ変数が格納しているアドレスが指すメモリ領域にある値を取得したり、変更したりすることができます。「〜が指す先の値」と読むと分かりやすいでしょう。

あれ? * はポインタ変数の宣言でも使いませんでしたか? 🤔 そうなんです。C言語では、* は文脈によって意味が変わります。

  • 宣言時 (例: int *ptr;): ptr がポインタ変数であることを示す目印。
  • 式の中 (例: value = *ptr;): ポインタ ptr が指す先の値にアクセスする演算子(間接参照)。

最初は少し混乱するかもしれませんが、使っていくうちに慣れていきますので安心してください。

では、間接参照演算子 * の使い方をコードで見てみましょう。

// 間接参照演算子の使用例
#include <stdio.h>

int main(void) {
    int price = 500;    // 商品の価格
    int *p_price;       // priceのアドレスを格納するためのポインタ変数

    p_price = &price; // p_priceにpriceのアドレスを代入

    printf("変数priceの値: %d\n", price);
    printf("ポインタp_priceが指すアドレス: %p\n", p_price);

    // 間接参照演算子 * を使って、p_priceが指す先の値を取得して表示
    printf("p_priceが指す先の値 (*p_price): %d\n", *p_price); // priceの値と同じになるはず

    // 間接参照演算子 * を使って、p_priceが指す先の値を変更してみる
    printf("\nセール開始!価格を100円引きにします。\n");
    *p_price = *p_price - 100; // p_priceが指す先の値から100を引く
    // これは price = price - 100; と同じ意味になる

    printf("変更後の変数priceの値: %d\n", price); // priceの値も変わっている!
    printf("変更後のp_priceが指す先の値 (*p_price): %d\n", *p_price);

    return 0;
}
      

実行結果の例です。

変数priceの値: 500
ポインタp_priceが指すアドレス: 0x7ffeeabc9abc
p_priceが指す先の値 (*p_price): 500

セール開始!価格を100円引きにします。
変更後の変数priceの値: 400
変更後のp_priceが指す先の値 (*p_price): 400
      

このコードでは、まず *p_price を使って、ポインタ p_price が指している場所の値(つまり price の値 500)を取得して表示しています。

次に、*p_price = *p_price - 100; という式で、p_price が指す先の値を変更しています。*p_priceprice と同じ場所を指しているので、この操作は変数 price の値を直接変更するのと同じ効果があります。その結果、price の値も 400 に変わっていることが確認できますね。

このように、間接参照演算子 * を使うと、ポインタ変数(アドレス)を通じて、元の変数に「間接的に」アクセスしたり、その値を変更したりできるのです。これがポインタの強力さの一端です 💪。

💡 整理:&* の関係

アドレス演算子 & と間接参照演算子 * は、互いに逆の操作をするペアだと考えると理解しやすいです。
  • &変数:変数からアドレスを取り出す。
  • *ポインタ変数:ポインタ変数(アドレス)からを取り出す(または値を書き換える)。
したがって、int num = 10; int *ptr = &num; のとき、*ptrnum と同じ値 (10) になりますし、*&numnum と同じ値 (10) になります(ただし *&num という書き方は通常しません)。

まとめ 📝

今回は、C言語のポインタの基本となる「アドレス演算子 &」と「間接参照演算子 *」、そしてそれらと密接に関わる「ポインタ変数」について学びました。

  • コンピュータのデータはメモリに格納され、各場所には固有のアドレスがある。
  • アドレス演算子 & は、変数のメモリアドレス(住所)を取得する。 (&num)
  • ポインタ変数は、メモリアドレスを格納するための特別な変数。 (int *ptr;)
  • 間接参照演算子 * は、ポインタ変数が指すアドレスにある値にアクセス(読み書き)する。 (*ptr)

これらの概念は、今後のポインタ学習(配列とポインタの関係、関数へのポインタ渡しなど)の基礎となります。最初は少し難しく感じるかもしれませんが、コードを書きながら実際に動かしてみることで、徐々に理解が深まっていくはずです。焦らず、一歩ずつ進んでいきましょう! 😊

少しだけ注意点 ⚠️

最後に、ポインタを扱う上で非常に重要な注意点を一つだけ挙げておきます。

初期化されていないポインタ変数(どこのアドレスも指していない、あるいは無効なアドレスを指しているポインタ)に対して、間接参照演算子 * を使ってはいけません。

// 危険なコードの例 (絶対に真似しないでください!)
#include <stdio.h>

int main(void) {
    int *p; // ポインタ変数pを宣言したが、どこも指していない(初期化されていない)
    // この時点でpには不定な値(ゴミのようなアドレス)が入っている可能性がある

    // *p = 100; // ← 非常に危険!どこのメモリか分からない場所に書き込もうとしている!
               //     プログラムがクラッシュする可能性が高い!

    // int value = *p; // ← これも危険!どこのメモリか分からない場所から読み取ろうとしている!

    return 0;
}
      

このような操作は、プログラムが予期せぬ動作をしたり、異常終了(クラッシュ)したりする原因となります。ポインタ変数を使うときは、必ず有効なメモリアドレスを指していることを確認してから、間接参照を行うようにしましょう。安全な方法の一つとして、ポインタが何も指していないことを示す特別な値 NULL で初期化したり、代入したりする方法がありますが、これについてはまた別の機会に詳しく説明しますね。

まずは、今回学んだ &* の基本操作に慣れることから始めましょう!💪 次回は、ポインタ変数のより詳しい使い方や注意点について見ていきます。

コメント

タイトルとURLをコピーしました