簡體   English   中英

C 和 C++ 中的靜態變量存儲在哪里?

[英]Where are static variables stored in C and 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 格式文件的工具鏈。 因此,我認為,必須一定的空間,為那些靜態變量的可執行文件保留。
出於討論目的,假設我們使用 GCC 工具鏈。

你的靜態去哪里取決於它們是否是零初始化的 零初始化靜態數據進入.BSS (Block Started by Symbol)非零初始化數據進入.DATA

當一個程序被加載到內存中時,它被組織成不同的段。 其中一個段是DATA 段 數據段進一步細分為兩部分:

初始化數據段:所有的全局、靜態和常量數據都存儲在這里。
未初始化數據段(BSS):所有未初始化的數據都存儲在該段中。

這是一個解釋這個概念的圖表:

在此處輸入圖片說明


這是解釋這些概念的非常好的鏈接:

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

實際上,變量是元組(存儲、作用域、類型、地址、值):

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 區域中(分別取決於它是否顯式初始化)。 然后將其范圍相應地限定為源文件中的所有函數或函數。

數據的存儲位置將取決於實現。

但是,靜態的含義是“內部鏈接”。 因此,符號在編譯單元 (foo.c, bar.c)內部,不能在該編譯單元外部引用。 因此,不會有名稱沖突。

我不相信會發生碰撞。 在文件級別(外部函數)使用 static 將變量標記為當前編譯單元(文件)的本地變量。 它永遠不會在當前文件之外可見,因此永遠不必具有可以在外部使用的名稱。

函數使用 static 是不同的 - 變量僅對函數可見(無論是否為靜態),它只是在調用該函數時保留其值。

實際上,靜態根據它所在的位置做了兩種不同的事情。 但是,在這兩種情況下,變量可見性都受到限制,因此您可以在鏈接時輕松防止命名空間沖突。

話雖如此,我相信它會存儲在DATA部分中,該部分往往具有初始化為非零值的變量。 當然,這是一個實現細節,不是標准強制要求的——它只關心行為,而不關心事情是如何在幕后完成的。

在“全局和靜態”區域:)

C++中有幾個內存區域:

  • 免費商店
  • 全局和靜態
  • 常量

請參閱此處詳細回答您的問題:

下面總結了 C++ 程序的主要不同內存區域。 請注意,某些名稱(例如,“堆”)在草案 [標准] 中並未出現。

     Memory Area     Characteristics and Object Lifetimes
     --------------  ------------------------------------------------

     Const Data      The const data area stores string literals and
                     other data whose values are known at compile
                     time.  No objects of class type can exist in
                     this area.  All data in this area is available
                     during the entire lifetime of the program.

                     Further, all of this data is read-only, and the
                     results of trying to modify it are undefined.
                     This is in part because even the underlying
                     storage format is subject to arbitrary
                     optimization by the implementation.  For
                     example, a particular compiler may store string
                     literals in overlapping objects if it wants to.


     Stack           The stack stores automatic variables. Typically
                     allocation is much faster than for dynamic
                     storage (heap or free store) because a memory
                     allocation involves only pointer increment
                     rather than more complex management.  Objects
                     are constructed immediately after memory is
                     allocated and destroyed immediately before
                     memory is deallocated, so there is no
                     opportunity for programmers to directly
                     manipulate allocated but uninitialized stack
                     space (barring willful tampering using explicit
                     dtors and placement new).


     Free Store      The free store is one of the two dynamic memory
                     areas, allocated/freed by new/delete.  Object
                     lifetime can be less than the time the storage
                     is allocated; that is, free store objects can
                     have memory allocated without being immediately
                     initialized, and can be destroyed without the
                     memory being immediately deallocated.  During
                     the period when the storage is allocated but
                     outside the object's lifetime, the storage may
                     be accessed and manipulated through a void* but
                     none of the proto-object's nonstatic members or
                     member functions may be accessed, have their
                     addresses taken, or be otherwise manipulated.


     Heap            The heap is the other dynamic memory area,
                     allocated/freed by malloc/free and their
                     variants.  Note that while the default global
                     new and delete might be implemented in terms of
                     malloc and free by a particular compiler, the
                     heap is not the same as free store and memory
                     allocated in one area cannot be safely
                     deallocated in the other. Memory allocated from
                     the heap can be used for objects of class type
                     by placement-new construction and explicit
                     destruction.  If so used, the notes about free
                     store object lifetime apply similarly here.


     Global/Static   Global or static variables and objects have
                     their storage allocated at program startup, but
                     may not be initialized until after the program
                     has begun executing.  For instance, a static
                     variable in a function is initialized only the
                     first time program execution passes through its
                     definition.  The order of initialization of
                     global variables across translation units is not
                     defined, and special care is needed to manage
                     dependencies between global objects (including
                     class statics).  As always, uninitialized proto-
                     objects' storage may be accessed and manipulated
                     through a void* but no nonstatic members or
                     member functions may be used or referenced
                     outside the object's actual lifetime.

如何使用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 指向以下指令,該指令在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。 在 BSS 中初始化數據,在 DATA 中未初始化。

靜態數據和全局數據之間的區別在於文件中包含符號信息。 編譯器傾向於包含符號信息,但只標記全局信息。

鏈接器尊重這些信息。 靜態變量的符號信息要么被丟棄要么被破壞,這樣靜態變量仍然可以以某種方式被引用(使用調試或符號選項)。 在這兩種情況下,編譯單元都不會受到影響,因為鏈接器首先解析本地引用。

我用 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

所以,也就是說,您的四個變量位於數據段事件中,名稱相同,但偏移量不同。

如前所述,靜態變量存儲在數據段或代碼段中。
您可以確定它不會在堆棧或堆上分配。
沒有發生沖突的風險,因為static關鍵字將變量的范圍定義為文件或函數,如果發生沖突,編譯器/鏈接器會警告您。
一個很好的例子

那么這個問題有點太老了,但是因為沒有人指出任何有用的信息:檢查“mohit12379”的帖子,解釋符號表中同名靜態變量的存儲: http : //www.geekinterview.com/question_details/ 24745

答案很可能取決於編譯器,因此您可能想要編輯您的問題(我的意思是,即使 ISO C 和 ISO C++ 都沒有強制要求段的概念)。 例如,在 Windows 上,可執行文件不帶有符號名稱。 一個 'foo' 將偏移 0x100,另一個可能是 0x2B0,並且編譯來自兩個翻譯單元的代碼,知道“他們的”foo 的偏移量。

它們都將獨立存儲,但是如果您想讓其他開發人員清楚地了解它們,您可能希望將它們包裝在命名空間中。

您已經知道它要么存儲在 bss(塊以符號開頭)中,也稱為未初始化數據段或已初始化數據段。

讓我們舉一個簡單的例子

void main(void)
{
static int i;
}

上面的靜態變量沒有初始化,所以它轉到未初始化的數據段(bss)。

void main(void)
{
static int i=10;
}

當然,它初始化為 10,所以它會進入初始化的數據段。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM