61

Cで、と呼ばれる変数があるとしましょうvariable_name。に0xaaaaaaaaあり、そのメモリアドレスに整数123があるとします。つまり、variable_name123が含まれています。

variable_name「は」にあるという言い回しの周りの説明を探してい0xaaaaaaaaます。コンパイラは、文字列「variable_name」がその特定のメモリアドレスに関連付けられていることをどのように認識しますか?文字列「variable_name」はメモリのどこかに保存されていますか?コンパイラはそれを見るたびに代用variable_nameするだけですか?もしそうなら、その代用を行うためにメモリを使用する必要はありませんか?0xaaaaaaaa

4

5 に答える 5

93

コンパイラの実行後、変数名は存在しなくなります(共有ライブラリにエクスポートされたグローバルやデバッグシンボルなどの特殊なケースを除く)。コンパイルの全体的な行為は、ソースコードによって表されるそれらのシンボリック名とアルゴリズムを取得し、それらをネイティブのマシン命令に変換することを目的としています。そうです、グローバル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します。しかし、ご覧のとおり、リテラルxy文字列はもう存在しません。それらはプログラマーであるあなたにとって便利でしたが、コンピューターは実行時にそれらを気にしません。

于 2013-01-30T19:44:51.430 に答える
13

ACコンパイラは、最初にシンボルテーブルを作成します。このテーブルには、変数名とそのメモリ内の場所との関係が格納されます。コンパイル時に、他の人が述べているように、このテーブルを使用して、変数のすべてのインスタンスを特定のメモリ位置に置き換えます。ウィキペディアのページでさらに多くのことを見つけることができます。

于 2013-01-30T19:47:37.867 に答える
8

すべての変数はコンパイラーによって置き換えられます。最初にそれらは参照で置き換えられ、後でリンカーは参照の代わりにアドレスを配置します。

言い換えると。コンパイラーが実行されるとすぐに、変数名は使用できなくなります

于 2013-01-30T19:45:31.903 に答える
6

これは、実装の詳細と呼ばれるものです。あなたが説明することは、私が今まで使用したすべてのコンパイラーに当てはまりますが、そうである必要はありません。ACコンパイラーはすべての変数をハッシュテーブルに入れて実行時にそれらを検索することができ(またはそのようなもの)、実際、初期のJavaScriptインタープリターはまさにそれを実行しました(現在、彼らはジャストインタイムコンパイルを実行し、より生の何かをもたらします)。

特にVC++、GCC、LLVMなどの一般的なコンパイラの場合:コンパイラは通常、メモリ内の場所に変数を割り当てます。グローバルスコープまたは静的スコープの変数は、プログラムの実行中に変更されない固定アドレスを取得しますが、関数内の変数はスタックアドレスを取得します。つまり、現在のスタックポインターに相対的なアドレスであり、関数が実行されるたびに変更されます。と呼ばれる。(これは過度に単純化されています。)スタックアドレスは、関数が戻るとすぐに無効になりますが、使用するオーバーヘッドが事実上ゼロになるという利点があります。

変数にアドレスが割り当てられると、変数の名前はそれ以上必要ないため、破棄されます。名前の種類によっては、前処理時(マクロ名の場合)、コンパイル時(静的およびローカル変数/関数の場合)、リンク時(グローバル変数/関数の場合)に名前が破棄される場合があります。シンボルがエクスポートされる場合(他のプログラムがアクセスできるように表示されるようにすると、名前は通常、「シンボルテーブル」のどこかに残りわずかな量のメモリとディスクスペースを占有します。

于 2013-01-30T19:46:46.153 に答える
4

コンパイラは、0xaaaaaaaaを検出するたびに、variable_nameを0xaaaaaaaaに置き換えますか?

はい。

もしそうなら、その置換を行うためにメモリを使用する必要はありませんか?

はい。しかし、それはコンパイラです。コードをコンパイルした後、なぜメモリを気にするのですか?

于 2013-01-30T19:45:35.810 に答える