Where are static variables stored in C and C++?
可执行文件的哪个段(.bss,.data,other)中存储了静态变量,以便它们不会发生名称冲突?例如:
1 2 3 4 5 6 7 8 | 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); } } |
如果我编译这两个文件并将其链接到一个重复调用footerst()和bartest的main,printf语句将独立递增。有意义,因为foo和bar变量是翻译单元的本地变量。
但是存储在哪里分配呢?
要清楚地说,假设您有一个工具链,可以以elf格式输出文件。因此,我认为在可执行文件中必须为这些静态变量保留一些空间。出于讨论目的,假设我们使用gcc工具链。
静态的位置取决于它们是否初始化为零。零初始化静态数据进入.bss(以符号开头的块),非零初始化数据进入.data
当一个程序装入内存时,它被组织成不同的段。其中一个段是数据段。数据段进一步分为两部分:初始化数据段:所有的全局、静态和常量数据都存储在这里。未初始化数据段(BSS):所有未初始化的数据都存储在此段中。
下面是一个图表来解释这个概念:
以下是解释这些概念的非常好的链接:
http://www.inf.udec.cl/~leo/teoX.pdf
实际上,变量是元组(存储、作用域、类型、地址、值):
1 2 3 4 5 | 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 |
局部作用域可以是局部到翻译单元(源文件)、函数或块,这取决于其定义的位置。要使变量对多个函数可见,它必须位于数据或BSS区域中(取决于其是否显式初始化)。然后它的作用域相应地限定为源文件中的所有函数或函数。
数据的存储位置取决于实现。
然而,静态的含义是"内部联系"。因此,符号是编译单元(foo.c,bar.c)的内部符号,不能在编译单元之外引用。所以,不会有名称冲突。
我不相信会有碰撞。在文件级使用static(外部函数)将变量标记为当前编译单元(文件)的本地变量。它在当前文件之外永远不可见,因此不必有名称。
在函数内部使用static是不同的-变量只对函数可见,它的值只在调用该函数时保留。
实际上,static根据其所在位置执行两种不同的操作。但是,在其他情况下,它会限制变量的可见性,以防止名称空间冲突,
尽管如此,我相信它将存储在倾向于初始化变量的数据中。BSS最初代表字节集-
如何与EDOCX1[0]一起找到它
要真正了解正在发生的事情,您必须了解链接器重新定位。如果你从未接触过这一点,请考虑先阅读这篇文章。
让我们分析一个Linux x86-64 ELF示例来了解它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #include <stdio.h> int f() { static int i = 1; i++; return i; } int main() { printf("%d ", f()); printf("%d ", f()); return 0; } |
编译:
1 | gcc -ggdb -c main.c |
用以下代码解压代码:
1 | objdump -Sr main.o |
-S 将代码分解为与原始源混合的代码。-r 显示搬迁信息
在
1 2 3 4 | 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 |
因为我们使用的是RIP相对寻址,所以存在
这是必需的,因为rip指向以下指令,该指令在
然后,如果我们将源代码修改为
static int i = 0 接.bss 。static int i = 1 接.data 。
在"全局和静态"区域中:)
C++中有几个内存区域
- 堆
- 免费商店
- 堆栈
- 全局和静态
- 康斯特
请参阅此处了解您的问题的详细答案
这取决于您使用的平台和编译器。一些编译器直接存储在代码段中。静态变量总是只能被当前翻译单元访问,并且不会导出名称,因此不会发生名称冲突。
编译单元中声明的数据将进入该文件输出的.bss或.data。已在BSS中初始化数据,未在数据中倾斜。
静态数据和全局数据之间的区别在于在文件中包含符号信息。编译器倾向于包含符号信息,但只标记全局信息。
链接器尊重此信息。静态变量的符号信息被丢弃或损坏,这样静态变量仍可以以某种方式引用(使用调试或符号选项)。在任何情况下,当链接器首先解析本地引用时,编译单元都不会受到影响。
这是如何(容易理解):
这个问题有点太老了,但由于没有人指出任何有用的信息:通过"mohit12379"查看帖子,解释符号表中同名静态变量的存储:http://www.geekinterview.com/questionu详细信息/24745
我用objdump和gdb尝试过,结果是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | (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结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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 |
所以,也就是说,四个变量位于数据节事件中,名称相同,但偏移量不同。
静态变量存储在前面提到的数据段或代码段中。您可以确保不会在堆栈或堆上分配它。由于
答案很可能取决于编译器,所以你可能想编辑你的问题(我的意思是,即使段的概念不是由ISOC和ISO C++授权的)。例如,在Windows上,可执行文件不带符号名。一个"foo"是偏移量0x100,另一个可能是0x2b0,编译来自两个翻译单元的代码时都知道"它们的"foo的偏移量。
它们都将独立存储,但是如果您想向其他开发人员清楚地表明这一点,您可能会希望将它们包装在名称空间中。
您已经知道它存储在BSS(以符号开头的块)中,也称为未初始化的数据段,或者存储在已初始化的数据段中。
让我们举个简单的例子
1 2 3 4 | void main(void) { static int i; } |
上述静态变量未初始化,因此将转到未初始化的数据段(BSS)。
1 2 3 4 | void main(void) { static int i=10; } |
当然,它是由10初始化的,所以它进入初始化的数据段。