web-dev-qa-db-ja.com

CおよびC ++の静的変数はどこに保存されますか?

名前の衝突がないように、実行可能ファイルのどのセグメント(.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ツールチェーンを使用すると仮定します。

162
Benoit

静的変数がどこに行くかは、それらがzero-initializedであるかどうかによって異なります。 zero-initialized静的データは 。BSS(Block Started by Symbol) 、nonzero-initializedデータが入る 。DATA

119
Don Neufeld

プログラムがメモリに読み込まれると、さまざまなセグメントに編成されます。セグメントの1つは データセグメント。データセグメントはさらに2つの部分に分割されます。

初期化されたデータセグメント: すべてのグローバルデータ、静的データ、および定数データが​​ここに格納されます。
初期化されていないデータセグメント(BSS): 初期化されていないデータはすべて、このセグメントに保存されます。

この概念を説明する図を次に示します。

enter image description here


これらの概念を説明する非常に良いリンクがあります:

http://www.inf.udec.cl/~leo/teoX.pdf

102
karn

実際、変数はタプル(ストレージ、スコープ、タイプ、アドレス、値)です:

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領域にある必要があります(それぞれ明示的に初期化されているかどうかによって異なります)。その後、すべての関数またはソースファイル内の関数のいずれかに応じてスコープが設定されます。

31
yogeesh

データの保存場所は実装に依存します。

ただし、staticの意味は「内部リンケージ」です。したがって、シンボルは、コンパイル単位(foo.c、bar.c)に対してinternalであり、そのコンパイル単位の外部で参照することはできません。したがって、名前の衝突はあり得ません。

21
Seb Rose

衝突が起こるとは思わない。ファイルレベル(関数の外部)でstaticを使用すると、変数が現在のコンパイル単位(ファイル)に対してローカルとしてマークされます。現在のファイルの外では決して見えないため、名前を付ける必要はありません。

関数内での静的の使用は異なります。変数は関数にのみ表示され、その値はその関数の呼び出し間で保持されます。

実際、staticは、場所に応じて2つの異なる処理を実行します。ただし、どちらの場合も、変数の可視性を制限して名前空間の衝突を防ぎ、

そうは言っても、変数に初期化される傾向があるDATAに格納されると思います。 BSSは元々、初期化されていない変数を保持するbyte-set- <something>の略でした。

13
paxdiablo

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相対アドレス指定を使用しているため、命令の%ripR_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になります

「グローバルおよび静的」エリア:)

c ++にはいくつかのメモリ領域があります

  • ヒープ
  • フリーストア
  • スタック
  • グローバルおよび静的
  • const

質問への詳細な回答については、 here を参照してください

9
ugasoft

使用しているプラ​​ットフォームとコンパイラに依存します。一部のコンパイラは、コードセグメントに直接格納します。静的変数は常に現在の翻訳単位にのみアクセス可能であり、名前はエクスポートされないため、名前の衝突が発生することはありません。

6
trotterdylan

コンパイル単位で宣言されたデータは、そのファイル出力の.BSSまたは.Dataに入ります。 DATAで初期化されていない、BSSで初期化されたデータ。

静的データとグローバルデータの違いは、ファイルにシンボル情報を含めることにあります。コンパイラはシンボル情報を含める傾向がありますが、グローバル情報のみをマークします。

リンカはこの情報を尊重します。静的変数のシンボル情報は破棄されるかマングルされるため、静的変数は何らかの方法で(デバッグまたはシンボルオプションを使用して)引き続き参照できます。どちらの場合も、リンカが最初にローカル参照を解決するため、コンパイル単位は影響を受けません。

5
itj

これは次のとおりです(理解しやすい):

stack, heap and static data

3
Yousha Aleayoub

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つの変数は、同じ名前のデータセクションイベントにありますが、オフセットが異なります。

2
Dan

まあ、この質問は少し古すぎますが、誰も有用な情報を指摘していないので、「mohit12379」による投稿をチェックして、シンボルテーブルに同じ名前の静的変数のストアを説明します: http:/ /www.geekinterview.com/question_details/24745

2
lukmac

前述のデータセグメントまたはコードセグメントに格納された静的変数。
スタックまたはヒープに割り当てられないことを確認できます。
staticキーワードは変数のスコープをファイルまたは関数に定義するため、衝突のリスクはありません。衝突の場合、警告するコンパイラ/リンカーがあります。
ナイス

2
Ilya

答えはコンパイラに大きく依存する可能性があるため、おそらく質問を編集する必要があります(つまり、セグメントの概念でさえISO CやISO C++では必須ではありません)。たとえば、Windowsでは、実行可能ファイルにはシンボル名が含まれていません。 1つの「foo」はオフセット0x100で、もう1つはおそらく0x2B0であり、両方の変換ユニットからのコードは「their」fooのオフセットを認識してコンパイルされます。

1
MSalters

どちらも独立して保存されますが、他の開発者に明確にしたい場合は、名前空間でラップすることをお勧めします。

0
Robert Gould