名前の衝突がないように、実行可能ファイルのどのセグメント(.BSS、.DATA、その他)に静的変数が格納されていますか?例えば:
foo.c: bar.c:
static int foo = 1; static int foo = 10;
void fooTest() { void barTest() {
static int bar = 2; static int bar = 20;
foo++; foo++;
bar++; bar++;
printf("%d,%d", foo, bar); printf("%d, %d", foo, bar);
} }
両方のファイルをコンパイルし、fooTest()とbarTestを繰り返し呼び出すmainにリンクすると、printfステートメントは独立して増加します。 foo変数とbar変数は翻訳単位に対してローカルであるため、意味があります。
しかし、ストレージはどこに割り当てられますか?
明確にするために、ELF形式でファイルを出力するツールチェーンがあることを前提としています。したがって、Ibelieveこれらの静的変数用の実行可能ファイルに予約されているスペースがあることをhas.
議論のために、GCCツールチェーンを使用すると仮定します。
静的変数がどこに行くかは、それらがzero-initializedであるかどうかによって異なります。 zero-initialized静的データは 。BSS(Block Started by Symbol) 、nonzero-initializedデータが入る 。DATA
プログラムがメモリに読み込まれると、さまざまなセグメントに編成されます。セグメントの1つは データセグメント。データセグメントはさらに2つの部分に分割されます。
初期化されたデータセグメント: すべてのグローバルデータ、静的データ、および定数データがここに格納されます。
初期化されていないデータセグメント(BSS): 初期化されていないデータはすべて、このセグメントに保存されます。
この概念を説明する図を次に示します。
これらの概念を説明する非常に良いリンクがあります:
実際、変数はタプル(ストレージ、スコープ、タイプ、アドレス、値)です:
storage : where is it stored, for example data, stack, heap...
scope : who can see us, for example global, local...
type : what is our type, for example int, int*...
address : where are we located
value : what is our value
ローカルスコープは、定義されている場所に応じて、翻訳単位(ソースファイル)、関数、またはブロックのいずれかにローカルであることを意味します。変数を複数の関数から見えるようにするには、必ずDATAまたはBSS領域にある必要があります(それぞれ明示的に初期化されているかどうかによって異なります)。その後、すべての関数またはソースファイル内の関数のいずれかに応じてスコープが設定されます。
データの保存場所は実装に依存します。
ただし、staticの意味は「内部リンケージ」です。したがって、シンボルは、コンパイル単位(foo.c、bar.c)に対してinternalであり、そのコンパイル単位の外部で参照することはできません。したがって、名前の衝突はあり得ません。
衝突が起こるとは思わない。ファイルレベル(関数の外部)でstaticを使用すると、変数が現在のコンパイル単位(ファイル)に対してローカルとしてマークされます。現在のファイルの外では決して見えないため、名前を付ける必要はありません。
関数内での静的の使用は異なります。変数は関数にのみ表示され、その値はその関数の呼び出し間で保持されます。
実際、staticは、場所に応じて2つの異なる処理を実行します。ただし、どちらの場合も、変数の可視性を制限して名前空間の衝突を防ぎ、
そうは言っても、変数に初期化される傾向があるDATAに格納されると思います。 BSSは元々、初期化されていない変数を保持するbyte-set- <something>の略でした。
objdump -Sr
で自分で見つける方法
実際に何が起こっているのかを理解するには、リンカーの再配置を理解する必要があります。これに触れたことがない場合は、 最初にこの投稿を読む を検討してください。
Linux x86-64 ELFの例を分析して、自分で確認してみましょう。
#include <stdio.h>
int f() {
static int i = 1;
i++;
return i;
}
int main() {
printf("%d\n", f());
printf("%d\n", f());
return 0;
}
コンパイル:
gcc -ggdb -c main.c
以下を使用してコードを逆コンパイルします。
objdump -Sr main.o
-S
は、元のソースが混在した状態でコードを逆コンパイルします-r
は再配置情報を示しますf
の逆コンパイルの内部には次のように表示されます。
static int i = 1;
i++;
4: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # a <f+0xa>
6: R_X86_64_PC32 .data-0x4
.data-0x4
は、.data
セグメントの最初のバイトに移動することを示しています。
-0x4
が存在するのは、RIP相対アドレス指定を使用しているため、命令の%rip
とR_X86_64_PC32
です。
RIPはfollowing命令を指しているため、これは再配置される00 00 00 00
の4バイト後に開始されるため、必要です。これについて詳しく説明しました: https://stackoverflow.com/a/30515926/895245
次に、ソースをi = 1
に変更して同じ分析を行うと、次のように結論付けられます。
static int i = 0
は.bss
になりますstatic int i = 1
は.data
になります使用しているプラットフォームとコンパイラに依存します。一部のコンパイラは、コードセグメントに直接格納します。静的変数は常に現在の翻訳単位にのみアクセス可能であり、名前はエクスポートされないため、名前の衝突が発生することはありません。
コンパイル単位で宣言されたデータは、そのファイル出力の.BSSまたは.Dataに入ります。 DATAで初期化されていない、BSSで初期化されたデータ。
静的データとグローバルデータの違いは、ファイルにシンボル情報を含めることにあります。コンパイラはシンボル情報を含める傾向がありますが、グローバル情報のみをマークします。
リンカはこの情報を尊重します。静的変数のシンボル情報は破棄されるかマングルされるため、静的変数は何らかの方法で(デバッグまたはシンボルオプションを使用して)引き続き参照できます。どちらの場合も、リンカが最初にローカル参照を解決するため、コンパイル単位は影響を受けません。
Objdumpとgdbで試してみましたが、結果は次のとおりです。
(gdb) disas fooTest
Dump of assembler code for function fooTest:
0x000000000040052d <+0>: Push %rbp
0x000000000040052e <+1>: mov %rsp,%rbp
0x0000000000400531 <+4>: mov 0x200b09(%rip),%eax # 0x601040 <foo>
0x0000000000400537 <+10>: add $0x1,%eax
0x000000000040053a <+13>: mov %eax,0x200b00(%rip) # 0x601040 <foo>
0x0000000000400540 <+19>: mov 0x200afe(%rip),%eax # 0x601044 <bar.2180>
0x0000000000400546 <+25>: add $0x1,%eax
0x0000000000400549 <+28>: mov %eax,0x200af5(%rip) # 0x601044 <bar.2180>
0x000000000040054f <+34>: mov 0x200aef(%rip),%edx # 0x601044 <bar.2180>
0x0000000000400555 <+40>: mov 0x200ae5(%rip),%eax # 0x601040 <foo>
0x000000000040055b <+46>: mov %eax,%esi
0x000000000040055d <+48>: mov $0x400654,%edi
0x0000000000400562 <+53>: mov $0x0,%eax
0x0000000000400567 <+58>: callq 0x400410 <printf@plt>
0x000000000040056c <+63>: pop %rbp
0x000000000040056d <+64>: retq
End of assembler dump.
(gdb) disas barTest
Dump of assembler code for function barTest:
0x000000000040056e <+0>: Push %rbp
0x000000000040056f <+1>: mov %rsp,%rbp
0x0000000000400572 <+4>: mov 0x200ad0(%rip),%eax # 0x601048 <foo>
0x0000000000400578 <+10>: add $0x1,%eax
0x000000000040057b <+13>: mov %eax,0x200ac7(%rip) # 0x601048 <foo>
0x0000000000400581 <+19>: mov 0x200ac5(%rip),%eax # 0x60104c <bar.2180>
0x0000000000400587 <+25>: add $0x1,%eax
0x000000000040058a <+28>: mov %eax,0x200abc(%rip) # 0x60104c <bar.2180>
0x0000000000400590 <+34>: mov 0x200ab6(%rip),%edx # 0x60104c <bar.2180>
0x0000000000400596 <+40>: mov 0x200aac(%rip),%eax # 0x601048 <foo>
0x000000000040059c <+46>: mov %eax,%esi
0x000000000040059e <+48>: mov $0x40065c,%edi
0x00000000004005a3 <+53>: mov $0x0,%eax
0x00000000004005a8 <+58>: callq 0x400410 <printf@plt>
0x00000000004005ad <+63>: pop %rbp
0x00000000004005ae <+64>: retq
End of assembler dump.
これがobjdumpの結果です
Disassembly of section .data:
0000000000601030 <__data_start>:
...
0000000000601038 <__dso_handle>:
...
0000000000601040 <foo>:
601040: 01 00 add %eax,(%rax)
...
0000000000601044 <bar.2180>:
601044: 02 00 add (%rax),%al
...
0000000000601048 <foo>:
601048: 0a 00 or (%rax),%al
...
000000000060104c <bar.2180>:
60104c: 14 00 adc $0x0,%al
つまり、4つの変数は、同じ名前のデータセクションイベントにありますが、オフセットが異なります。
まあ、この質問は少し古すぎますが、誰も有用な情報を指摘していないので、「mohit12379」による投稿をチェックして、シンボルテーブルに同じ名前の静的変数のストアを説明します: http:/ /www.geekinterview.com/question_details/24745
前述のデータセグメントまたはコードセグメントに格納された静的変数。
スタックまたはヒープに割り当てられないことを確認できます。static
キーワードは変数のスコープをファイルまたは関数に定義するため、衝突のリスクはありません。衝突の場合、警告するコンパイラ/リンカーがあります。
ナイス 例
答えはコンパイラに大きく依存する可能性があるため、おそらく質問を編集する必要があります(つまり、セグメントの概念でさえISO CやISO C++では必須ではありません)。たとえば、Windowsでは、実行可能ファイルにはシンボル名が含まれていません。 1つの「foo」はオフセット0x100で、もう1つはおそらく0x2B0であり、両方の変換ユニットからのコードは「their」fooのオフセットを認識してコンパイルされます。
どちらも独立して保存されますが、他の開発者に明確にしたい場合は、名前空間でラップすることをお勧めします。