![](/img/trans.png)
[英]C++ variables and where they are stored in memory (stack, heap, static)
[英]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):所有未初始化的數據都存儲在該段中。
這是一個解釋這個概念的圖表:
這是解釋這些概念的非常好的鏈接:
實際上,變量是元組(存儲、作用域、類型、地址、值):
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 相對尋址,因此指令中的%rip
和R_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.