C++ 「参照」の正体は実はポインタであるという説の検証結果

C++
この記事は約7分で読めます。

こんにちは、ナナです。

この記事は『C++ 参照【関数におけるポインタ渡しと参照渡しの違い】』のおまけとして書いたものです。

C++を扱う上では知らなくてもいいのではないかと思っている内容ですので、興味のある方のみお読みください。

改めて説明すると「参照」とは

とある変数ラベルに対して、異なるラベルを貼りつけることができる機能

であるということです。

この概念は理解できるのですが、私の最初の印象は「違和感」です。「本当にそんなことできるの?」という騙されているような感覚です。

この機能を言語レベルでどのように実現しているか?という点について感覚的に腑に落ちないのです。

結論から言うと、

「参照」とは結局は「ポインタ」で実現されている

という内容になっています。

今回の検証内容は「Visual Stuidio 2019」にて行っています。コンパイラの違いなどによってひょっとしたら異なる実装もあるかもしれません。

本記事で学習できること
  • 参照で定義した変数はメモリが割り当てられないのか?
  • 参照変数のメモリに格納される情報とは?
  • なぜ「参照」は別ラベルを貼ったように見えるのか?
スポンサー

参照変数はメモリ領域が確保されない?についての考察

「参照」とは、変数につける別ラベルであるとするならば、参照変数を定義してもメモリは確保されないはずです。

参照変数のメモリは確保されない

図で示す通り、これこそがエイリアス(別名)と言われる理由ですね。

この事実が本当かどうかをわかりやすく確認するために、グローバル変数として静的メモリ上に同様の変数定義を行ってみます。

#include <stdio.h>

long num = 50;
long & refnum = num;

int main()
{
    printf("num   :0x%p\n", &num);
    printf("refnum:0x%p\n", &refnum);

    return 0;
}
num   :0x0079A000
refnum:0x0079A000

確かに、参照変数である「refnum」の番地は「num」変数と同一番地との結果です。

それでは次に、mapファイルでも「num」「refnum」がどのように割り当てられるのかを確認してみます。mapファイルは、リンカが生成するシンボルのメモリ割り当てを一覧化したファイルです。

・・・
 0003:00001dd0       ___rtc_tzz                 00418dd0     MSVCRTD:initsect.obj
 0004:00000000       ?num@@3JA                  0041a000     main.obj
 0004:00000004       ?refnum@@3AAJA             0041a004     main.obj
 0004:0000000c       ?_RTC_ErrorLevels@@3PAHA   0041a00c     MSVCRTD:error.obj
・・・

printf関数での表示アドレスとは異なりますが、mapファイル上では「refnum」が「num」とは異なるメモリ(0x0041a004)に割りついているのがわかります。

ナナ
ナナ

printfの結果とのアドレスが異なるのは、おそらく仮想メモリによる仕業かと思いますが、詳細な理由はわかりません。

ただし、mapファイル上は別領域で参照変数にもメモリが割りついているということなんです。

スポンサー

参照変数用のメモリに格納される情報とは?

参照変数には実はメモリが割り当てられているということがわかりました。

では、このメモリにはどんな情報が管理されているのでしょうか?

結果をわかりやすくするため、「num」「refnum」「tmp」の3つを変数定義してみましょう。初期値は、次のように目立つようにしておきます。

#include <stdio.h>

long num = 0x01234567;
long & refnum = num;
long tmp = 0x89ABCDEF;

int main()
{
    printf("num   :0x%p\n", &num);
    printf("refnum:0x%p\n", &refnum);
    printf("tmp   :0x%p\n", &tmp);

    return 0;
}
num   :0x0035A000
refnum:0x0035A000
tmp   :0x0035A008

それでは、デバッガのメモリウィンドウを使って、実際のメモリの中身を考察してみましょう。

参照変数のメモリに入っている情報

「refnum」が存在すると思われるメモリ場所には、なんと「num」変数の番地情報が格納されているのがわかりますね。

番地情報を格納するメモリといえば、それは「ポインタ変数」ということです。

ナナ
ナナ

わかりやすくグローバル変数にて確認していますが、ローカル変数でも同様の結果が得られます。

このように、「参照」の正体は実は「ポインタ変数」であるということなんです。

スポンサー

参照変数はなぜ別ラベルを貼ったかのように見えるのか?

「参照」の正体は「ポインタ」ということなんですが、プログラマーの目には「別のラベルを変数に貼り付けた」という姿として映ります。

なぜ、こんなことが可能なのでしょうか?

「参照」を「ポインタ変数」を使って表現してみる

次のプログラムは「参照」を使ったプログラムを「ポインタ変数」に変更して表現したものです。

int main()
{
    int num = 100;
    int& refnum = num;

    refnum = 200;

    printf("num    :%d\n", num);
    printf("refnum :%d\n", refnum);

    printf("&num   :0x%p\n", &num);
    printf("&refnum:0x%p\n", &refnum);

    return 0;
}
num    :200
refnum :200
&num   :0x00F8FA80
&refnum:0x00F8FA80
int main()
{
    int num = 100;
    int * refnum = &num

    *refnum = 200;

    printf("num    :%d\n", num);
    printf("refnum :%d\n", *refnum);

    printf("&num   :0x%p\n", &num);
    printf("&refnum:0x%p\n", &(*refnum));

    return 0;
}
num    :200
refnum :200
&num   :0x00BEFA2C
&refnum:0x00BEFA2C

「num」と「refnum」は同一番地としてどちらも表示されています。つまり、「参照」のプログラムは「ポインタ」のプログラムで代替可能ということなのです。

ポインタを「参照」に見せるコンパイラの技

このギミックを成立させているのは「コンパイラ」です。コンパイラの手によって「参照」のプログラムはこっそりと「ポインタ変数」として変換されるということです。

参照はポインタに変換される
ナナ
ナナ

皆さんの目には新しい「参照」という機能が追加されたように思わせながら、実は内部的には「ポインタ」を使って実現しているというコンパイラの巧みな技なのです。

スポンサー
C++機能解説
スポンサー
モノづくりC言語塾