Cでは、variable_name
という変数があるとします。それが0xaaaaaaaa
にあり、そのメモリアドレスに整数123があるとします。つまり、variable_name
には123が含まれています。
「variable_name
は0xaaaaaaaa
にあります」という言い回しの説明を探しています。文字列「variable_name」がその特定のメモリアドレスに関連付けられていることをコンパイラはどのように認識しますか?文字列「variable_name」はメモリのどこかに保存されていますか?コンパイラは、それを見たときはいつでもvariable_name
を0xaaaaaaaa
に置き換えますか?そうであれば、その置き換えを行うためにメモリを使用する必要はありませんか?
コンパイラーの実行後、変数名はもう存在しません(共有ライブラリーまたはデバッグシンボルでエクスポートされたグローバルのような特別な場合を除きます)。コンパイルの全体の動作は、ソースコードで表されるこれらのシンボリック名とアルゴリズムを取得して、ネイティブマシン命令に変換することを目的としています。つまり、グローバルvariable_name
、そしてコンパイラとリンカはそれを0xaaaaaaaa
、コード内で使用されている場合は常に、そのアドレスを介してアクセスされます。
文字通りの質問に答えるために:
コンパイラは、文字列「variable_name」がその特定のメモリアドレスに関連付けられていることをどのように認識しますか?
ツールチェーン(コンパイラとリンカ)が連携して、変数のメモリ位置を割り当てます。すべての参照を追跡するのはコンパイラの仕事であり、リンカは後で正しいアドレスを配置します。
文字列
"variable_name"
メモリのどこかに保存されていますか?
compilerの実行中のみ。
コンパイラは
variable_name
ために0xaaaaaaaa
それを見るときはいつでも、もしそうなら、それを置き換えるためにメモリを使用する必要はないでしょうか?
はい、それはリンカでの2段階のジョブであることを除いて、ほとんど何が起こるかです。そして、はい、それはメモリを使用しますが、それはcompiler'sメモリであり、プログラムの実行時のものではありません。
例が理解に役立つ場合があります。このプログラムを試してみましょう:
int x = 12;
int main(void)
{
return x;
}
かなり簡単ですよね? OK。このプログラムを取り、コンパイルして逆アセンブリを見てみましょう。
$ 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
現在、2つのmovl
命令とaddl
命令があります。最初のmovl
がy
を初期化していることがわかります。これはスタック(ベースポインター-4)上にあると判断されています。次に、次のmovl
がグローバルx
をレジスタeax
に取得し、addl
がその値にy
を追加します。しかし、ご覧のとおり、リテラルx
およびy
文字列はもう存在しません。それらはプログラマーであるyoにとって便利でしたが、コンピューターは実行時にそれらのことを気にしません。
Cコンパイラは最初に、変数名とメモリ内の場所との関係を格納するシンボルテーブルを作成します。他の人が述べたように、コンパイル時にこのテーブルを使用して、変数のすべてのインスタンスを特定のメモリ位置に置き換えます。あなたはウィキペディアのページでそれについてもっとたくさん見つけることができます。
すべての変数はコンパイラによって置き換えられます。最初に参照で置き換えられ、後でリンカが参照の代わりにアドレスを配置します。
言い換えると。コンパイラーが実行されるとすぐに、変数名は使用できなくなります
これは実装の詳細と呼ばれるものです。あなたが説明するのは、私が今まで使ったすべてのコンパイラの場合ですが、そうである必要はありません。 Cコンパイラーはすべての変数をハッシュテーブルに入れて、実行時に(またはそのようなもの)を調べ、実際、初期のJavaScriptインタープリターはそれを正確に実行していました(現在、Just-In-Timeコンパイルを実行するため、結果はずっと生になっています)。
特にVC++、GCC、LLVMなどの一般的なコンパイラーの場合:コンパイラーは通常、変数をメモリー内の場所に割り当てます。グローバルまたは静的スコープの変数は、プログラムの実行中に変更されない固定アドレスを取得します。一方、関数内の変数はstackアドレス、つまり、現在のスタックポインターに関連するアドレスを取得します。これは、関数が呼び出されるたびに変化します。 (これは単純化しすぎです。)スタックアドレスは、関数が戻るとすぐに無効になりますが、使用するオーバーヘッドが実質的にゼロになるという利点があります。
変数にアドレスが割り当てられると、変数の名前はそれ以上必要ないため、破棄されます。名前の種類に応じて、名前は前処理時(マクロ名の場合)、コンパイル時(静的およびローカル変数/関数の場合)、およびリンク時(グローバル変数/関数の場合)、およびシンボルがエクスポートされる場合(他のプログラムがアクセスできるように他のプログラムから見えるようにします)、名前は通常、「シンボルテーブル」のどこかに残りますdoesは、わずかな量のメモリとディスク容量を消費します。
コンパイラーは、それを見つけたときは常に0xaaaaaaaaをvariable_nameに置き換えますか?
はい。
もしそうなら、それを置き換えるためにメモリを使用する必要はないでしょうか?
はい。しかし、それはコンパイラです。コードをコンパイルした後、なぜメモリを気にするのですか?