コンパイラの実行後、変数名は存在しなくなります(共有ライブラリにエクスポートされたグローバルやデバッグシンボルなどの特殊なケースを除く)。コンパイルの全体的な行為は、ソースコードによって表されるそれらのシンボリック名とアルゴリズムを取得し、それらをネイティブのマシン命令に変換することを目的としています。そうです、グローバルvariable_name
があり、コンパイラとリンカがそれをに配置することを決定した場合0xaaaaaaaa
、コードで使用されている場所はどこでも、そのアドレスを介してアクセスされます。
だからあなたの文字通りの質問に答えるために:
コンパイラは、文字列「variable_name」がその特定のメモリアドレスに関連付けられていることをどのように認識しますか?
ツールチェーン(コンパイラとリンカ)が連携して、変数のメモリ位置を割り当てます。すべての参照を追跡するのはコンパイラの仕事であり、リンカは後で正しいアドレスを入力します。
文字列"variable_name"
はメモリのどこかに保存されていますか?
コンパイラの実行中のみ。
コンパイラはそれを見るたびに代用variable_name
するだけですか?もしそうなら、その代用を行うためにメモリを使用する必要はありませんか?0xaaaaaaaa
はい、それはリンカーを使用した2段階のジョブであることを除いて、ほとんど何が起こるかです。はい、それはメモリを使用しますが、それはコンパイラのメモリであり、プログラムの実行時に何も使用しません。
例はあなたが理解するのを助けるかもしれません。このプログラムを試してみましょう:
int x = 12;
int main(void)
{
return x;
}
かなり簡単ですよね?わかった。このプログラムを取得してコンパイルし、逆アセンブルを見てみましょう。
$ cc -Wall -Werror -Wextra -O3 example.c -o example
$ otool -tV example
example:
(__TEXT,__text) section
_main:
0000000100000f60 pushq %rbp
0000000100000f61 movq %rsp,%rbp
0000000100000f64 movl 0x00000096(%rip),%eax
0000000100000f6a popq %rbp
0000000100000f6b ret
そのmovl
行を見ますか?グローバル変数を取得しています(この場合、命令ポインターの相対的な方法で)。これ以上の言及はありませんx
。
それでは、もう少し複雑にして、ローカル変数を追加しましょう。
int x = 12;
int main(void)
{
volatile int y = 4;
return x + y;
}
このプログラムの逆アセンブリは次のとおりです。
(__TEXT,__text) section
_main:
0000000100000f60 pushq %rbp
0000000100000f61 movq %rsp,%rbp
0000000100000f64 movl $0x00000004,0xfc(%rbp)
0000000100000f6b movl 0x0000008f(%rip),%eax
0000000100000f71 addl 0xfc(%rbp),%eax
0000000100000f74 popq %rbp
0000000100000f75 ret
movl
これで、2つの命令と1つの命令がありaddl
ます。movl
最初は初期化中であり、スタック上にあると判断されていることがわかりy
ます(ベースポインタ-4)。次に、次movl
はグローバルx
をレジスタeax
に取得し、その値にaddl
追加y
します。しかし、ご覧のとおり、リテラルx
とy
文字列はもう存在しません。それらはプログラマーであるあなたにとって便利でしたが、コンピューターは実行時にそれらを気にしません。