C++入門|変数のアドレスとポインタ

値を見るだけでは半人前。C++の奥義は、変数の居場所をつかむことから始まる。

ここからは、C++の中でも最初の大きな山場になりやすい、変数のアドレスポインタについて見ていきます。

変数というと、これまでは数値や文字列を入れておく箱として考えてきました。
たとえば、整数を入れる変数には 100 や 200 のような値を入れて使っていましたよね。

でも、C/C++の世界では、それだけではありません。
変数には何の値が入っているかだけでなく、メモリ上のどこに置かれているかという情報もあります。

ドラゴンボールの世界観でたとえるなら、戦士がカプセルハウスに住んでいるようなものです。
戦士本人が「値」、その住んでいる住所が「アドレス」です。
そして、その住所メモを持っていて、そこにいる戦士へ直接アクセスできる仕組みがポインタです。

ポインタは最初は少しとっつきにくく感じますが、考え方を順番に整理すると、ちゃんと理解できます。

このテーマでは、特に次の3つを押さえることが大切です。

ポイント内容
変数のアドレス変数がメモリ上のどこにあるかを表す
ポインタアドレスを入れておく特別な変数
間接操作ポインタを通して元の変数の値を変更できる

ポインタが分かるようになると、C++の仕組みをかなり深く理解できるようになります。
関数に値を渡すだけでなく、元の変数そのものを操作する考え方にもつながっていくので、ここでしっかり基礎を固めていきましょう。

変数には値と居場所の2つがある

これまで学んできた変数は、たとえば int型の変数に整数値を入れて使うものでした。

でも実際には、変数はコンピュータのメモリの中に置かれています。
つまり、変数にはだけでなく、メモリ上の位置もあります。

このメモリ上の位置を表す数値がアドレスです。

たとえば、変数 a があるとき、次のように書くと a のアドレスを取得できます。

&a

先頭の & は、その変数のアドレスを取り出す演算子です。

ドラゴンボール風にたとえるなら、a が戦士本人だとすると、&a はその戦士がいる修行部屋の座標をスカウターで読み取るようなイメージです。

変数のアドレスを表示するサンプル

まずは、変数の値、大きさ、アドレスを表示するプログラムを見てみましょう。

プロジェクト/ファイル名: Chap2_12/main.cpp

#include <iostream>

using namespace std;

int main(int argc, char** argv) {
    // 戦闘力を表す整数型の変数
    int power = 100;

    cout << "powerの値は" << power;
    cout << "、大きさは" << sizeof(power) << "byte、";
    cout << "アドレスは" << std::hex << &power << "です。" << endl;

    return 0;
}

実行結果の例は、次のようになります。

powerの値は100、大きさは4byte、アドレスは61ff08です。

※ アドレス部分は、実行環境や実行のたびに異なることがあります。

sizeofで変数の大きさを調べる

このプログラムの中で、まず注目したいのが sizeof です。

sizeof(power)

sizeof は、その型や変数が何バイトの大きさを使うかを調べるための演算子です。

たとえば、次のような使い方ができます。

sizeof(int)
sizeof(power)

どちらも int型の大きさを調べる書き方です。

一般的には、int型は多くの環境で 4 バイトですが、厳密には環境によって異なることもあります。
そのため、プログラムでサイズを確認できる sizeof はとても便利です。

図:変数の値・サイズ・アドレスの関係

この図が示していること

この図から分かるのは、1つの変数には単に値が入っているだけでなく、どれくらいの大きさを使っているかメモリ上のどこに置かれているかという情報もあることです。

C++では、sizeof で大きさを調べられますし、&変数名 でアドレスを取得できます。
ポインタを理解するためには、まずこの「変数には住所がある」という感覚がとても大事です。

&を付けるとアドレスが取れる

次に注目したいのが、アドレスを取得しているこの部分です。

&power

power は変数そのものを表します。
一方で、&power は power のアドレスを表します。

ここはとても大事なので、表で整理しておきましょう。

書き方意味
power変数 power に入っている値
&power変数 power のアドレス

つまり、power が 100 だとしても、&power は 100 ではありません。
&power は「100 が入っている場所」を表す数値です。

アドレスを16進数で表示する理由

プログラムでは、アドレスを表示するときに std::hex を使っています。

std::hex

これは、整数を16進数で表示するための指定です。

メモリのアドレスは、普段は16進数で扱うことが多いため、アドレス表示の前に std::hex を入れています。

たとえば、10進数で長々と表示するよりも、16進数のほうがメモリ位置を見やすく整理できます。

ドラゴンボール風にたとえるなら、修行場の区画番号を通常の数字ではなく、スカウター専用の座標表記で見ている感じです。

ポインタはアドレスを入れるための特別な変数

ここまでで、変数にはアドレスがあることが分かりました。
次は、そのアドレスを入れておくための特別な変数を見ていきます。

それがポインタです。

普通の変数は、値を入れるための箱です。
たとえば int a; なら、a には整数値が入ります。

一方、ポインタはアドレスを入れる箱です。

int型のポインタ変数は、次のように宣言します。

int* p;

または、次のように書いても同じ意味です。

int *p;

どちらの書き方でも問題ありません。

ここでの p は、int型の変数のアドレスを入れるためのポインタ変数です。

通常の変数とポインタ変数の違い

ここは混乱しやすいので、比較表で見てみましょう。

形態通常の変数ポインタ変数解説
宣言int a;int* p;ポインタは型のあとに * を付ける
a*p*p は、p が指している先の値
アドレス&app 自体がアドレスを入れる

この表で特に大事なのは次の3つです。

  • a は値そのもの
  • &a は a のアドレス
  • p はアドレスを持つ
  • *p は、そのアドレス先にある値を表す

ここがポインタの核心です。

図:通常の変数とポインタ変数の違い

この図が示していること

この図では、通常の変数とポインタ変数の役割の違いを表しています。

通常の変数 a は値そのものを持っています。
一方でポインタ変数 p は、a のアドレスを持ちます。
そして *p と書くと、そのアドレスの先にある値、つまり a の値を取り出せます。

つまり、ポインタは「値を直接持つ」のではなく、「値がある場所を知っている変数」だと考えると分かりやすいです。

ポインタを使うサンプル

では実際に、ポインタを使うプログラムを見てみましょう。

プロジェクト/ファイル名: Chap2_13/main.cpp

#include <iostream>

using namespace std;

int main(int argc, char** argv) {
    // 戦闘力を表す通常の変数
    int power = 100;

    // int型のポインタ変数をNULLで初期化する
    int* p = NULL;

    // 元の変数の値を表示する
    cout << "power=" << power << endl;

    // ポインタにpowerのアドレスを代入する
    p = &power;

    // ポインタが指す先の値を表示する
    cout << "*p=" << *p << endl;

    // ポインタ経由で値を変更する
    *p = 200;
    cout << "*pの値を200に変更しました。" << endl;

    // 元の変数の値を再表示する
    cout << "power=" << power << endl;

    return 0;
}

実行結果は、次のようになります。

power=100
*p=100
*pの値を200に変更しました。
power=200

NULLで初期化する意味

まず、この行を見てください。

int* p = NULL;

これは、ポインタ変数 p を NULL で初期化しています。

NULL は、どこも指していない状態を表す特別な値です。
数値でいう 0 を意味しますが、ポインタでは「有効なアドレスがまだ入っていません」という意味で使われます。

ドラゴンボール風にたとえるなら、まだ修行部屋の座標がセットされていないスカウターのような状態です。

ポインタは、適当な値が入ったままだととても危険です。
そのため、最初は NULL で初期化しておくのが基本です。

補足

現代のC++では NULL の代わりに nullptr を使うことも多いですが、ここではポインタの基本概念を分かりやすくするために NULL で考えています。

p = &power で何が起きるのか

次の行が、ポインタのいちばん大事な部分です。

p = &power;

これは、power のアドレスを p に代入しています。

つまり、p は power が置かれている場所を知ることになります。

この時点で、p は「power の住所メモ」を持った状態です。
そのため、p を通して power の値にたどり着けるようになります。

*p は、指している先の値を表す

次の行では、*p を表示しています。

cout << "*p=" << *p << endl;

ここでの *p は、p の中に入っているアドレスの先にある値を意味します。

p には power のアドレスが入っているので、*p は power の値になります。
つまり、この時点では *p も 100 です。

ここは非常に重要です。

書き方意味
pアドレスそのもの
*pそのアドレス先にある値

この違いをしっかり区別できるようになると、ポインタがかなり分かりやすくなります。

*p = 200 で元の変数が変わる理由

次の行では、*p に 200 を代入しています。

*p = 200;

これは、p が指している先にある値を書き換えるという意味です。

p は power のアドレスを持っているので、*p = 200; は、実質的には power = 200; と同じことをしています。

そのため、最後に power を表示すると 200 になります。

cout << "power=" << power << endl;

最初は power に 100 を代入しただけで、そのあと直接 power = 200; とは書いていません。
それでも値が変わるのは、ポインタを通して元の変数を操作したからです。

これはまるで、修行場の部屋番号を指定して、その部屋にあるスカウターの設定値を直接書き換えているようなイメージです。

図:ポインタを通して元の変数を変更する流れ

この図が示していること

この図から分かるのは、ポインタは単にアドレスを保存しているだけではなく、そのアドレスの先にある値へアクセスできるということです。

p = &power で、p は power を指すようになります。
そのあと *p = 200 とすると、p が指している先、つまり power の値が 200 に変わります。

ここを理解できると、ポインタの実用的な使い方が一気に見えてきます。

p と *p の違いを整理しておこう

ポインタでは、p と *p を混同しやすいです。
ここは何度も確認しておきたいところです。

p の意味

p は、アドレスを入れているポインタ変数です。

たとえば、p = &power; のあとで p を表示したら、power のアドレスが表示されます。

*p の意味

*p は、p が指している先にある値です。

p に power のアドレスが入っているなら、*p は power の値と同じになります。

&power の意味

&power は、power のアドレスを取得する式です。

これを p に入れることで、p が power を指せるようになります。

覚え方のコツ

感覚的には、次のように覚えると分かりやすいです。

表現イメージ
power戦士本人
&power戦士のいる修行部屋の住所
pその住所をメモした紙
*p住所をたどって見つけた戦士本人

このイメージが持てると、ポインタの記号がかなり自然に読めるようになります。

ポインタを使うと何がうれしいのか

ここまで読むと、わざわざアドレスなんて使わなくても、普通に変数を使えばいいのではと思うかもしれません。

たしかに、簡単なプログラムでは通常の変数だけでも十分です。
でも、C/C++ではポインタがとても重要です。

主な理由は次のとおりです。

便利な点内容
元の変数を直接操作できる関数の中から呼び出し元の値を変更しやすい
メモリを柔軟に扱える動的メモリや配列処理などに使える
高速で効率的な処理につながる大きなデータを丸ごとコピーせずに扱える

特に、関数にポインタを渡すという考え方はとても大事です。

普通に関数へ値を渡すと、関数の中ではその値のコピーを使います。
でも、ポインタを渡せば、元の変数の住所を知らせることができます。
すると、関数の中から元の変数そのものを変更できるようになります。

今回の *p = 200; という操作は、その考え方の土台になっています。

アドレスとポインタはセットで理解しよう

ここまでの内容を見てくると、アドレスとポインタは切り離して考えないほうが分かりやすいです。

まず、変数には値とアドレスがあります。
そして、そのアドレスを入れるための変数がポインタです。
ポインタにアドレスを代入すると、その先にある元の変数へアクセスできるようになります。

順番に整理すると、流れは次のようになります。

手順内容
1通常の変数を用意する
2その変数のアドレスを & で取得する
3そのアドレスをポインタに代入する
4*ポインタ で元の変数の値を読む
5*ポインタ に代入して元の変数の値を変更する

この流れを頭の中でイメージできるようになると、ポインタの基本はかなりつかめています。

最初につまずきやすいポイント

ポインタ学習では、次の点で混乱しやすいです。

変数そのものとアドレスを混同する

power と &power は別物です。
power は値、&power は住所です。

ポインタそのものと指し先の値を混同する

p は住所、*p はその住所の先にある値です。
ここをごちゃごちゃにしないことが大切です。

初期化せずに使ってしまう

ポインタは必ず最初に NULL などで初期化しておくクセをつけると安全です。

記号だけで覚えようとしてしまう

記号だけ暗記しようとすると苦しくなります。
「住所を取り出す」「住所を持つ」「住所の先の値を見る」という意味で理解すると、かなり楽になります。

ポインタの基礎が分かると次につながる

この段階でいちばん大切なのは、ポインタを難しい特殊技だと思い込みすぎないことです。
実際には、やっていることはとてもシンプルです。

  • 変数には住所がある
  • その住所を持つのがポインタ
  • ポインタを使うと、その住所の先にある値を扱える

たったこれだけです。

ドラゴンボール風にいえば、戦士そのものを見るだけでなく、戦士がどの修行部屋にいるかを把握し、その部屋を指定して直接トレーニング内容を変更できるようになる感覚です。

この考え方が身につくと、この先の関数へのポインタ渡しや、メモリの生成と消去の学習もぐっと理解しやすくなります。