静态variables存储在哪里(在C / C ++中)?
在一个可执行文件的段(.BSS,.DATA,其他)是存储的静态variables,以便它们没有名称冲突? 例如:
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和barvariables是翻译单元的局部variables。
但是,存储分配在哪里?
要清楚,假设是你有一个工具链,将输出一个ELF格式的文件。 因此,我相信这些静态variables的可执行文件中必须保留一些空间。
为了讨论的目的,我们假设我们使用GCC工具链。
你的静态去的地方取决于它们是否被初始化。 0初始化静态数据进入.BSS(符号启动块) ,非0初始化数据进入.DATA
当一个程序被加载到内存中时,它被组织成不同的段。 其中一个部分是数据段 。 数据段进一步细分为两部分:
初始化的数据段:所有的全局,静态和常量数据存储在这里。
未初始化的数据段(BSS):所有未初始化的数据都存储在这个段中。
这里是解释这个概念的图表:
这里是解释这些概念的很好的链接:
事实上,一个variables是元组(存储,范围,types,地址,值):
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
局部作用域可能意味着本地到翻译单元(源文件),函数或块取决于它定义的位置。 要使variables对多个函数可见,它必须位于DATA或BSS区域(取决于是否分别初始化)。 然后将其作用于源文件中的所有function或function。
数据的存储位置将取决于实施。
但是, 静态的含义是“内部联动”。 因此,符号在编译单元(foo.c,bar.c) 内部,不能在编译单元之外被引用。 所以,不会有名称冲突。
我不相信会有碰撞。 在文件级使用静态(外部函数)将variables标记为当前编译单元(文件)的本地。 在当前文件外面永远不可见,所以永远不必有名字。
在一个函数中使用静态是不同的 – variables只对函数可见,只是它的值在对该函数的调用中被保留。
实际上,静态根据它在哪里做两件不同的事情。 然而,在其他情况下,它会限制variables的可见性以防止名称空间冲突,
话虽如此,我相信它会被存储在数据,往往已初始化variables。 BSS最初代表了字节集<something>,其中包含未初始化的variables。
这取决于你正在使用的平台和编译器。 一些编译器直接存储在代码段中。 静态variables总是只能被当前的翻译单元访问,并且名称不会被导出,因此永远不会出现原因名称冲突。
如何用objdump -Sr
自己find它
要真正理解正在发生的事情,您必须了解链接器重定位。 如果你从来没有碰到过,请考虑先阅读这篇文章 。
我们来分析一个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
段的第一个字节。
因为我们正在使用RIP相对寻址,因此在指令和R_X86_64_PC32
的%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中未初始化。
静态数据和全局数据之间的区别在于文件中包含符号信息。 编译器倾向于包含符号信息,但仅标记全局信息。
链接器尊重这些信息。 静态variables的符号信息被丢弃或者被破坏,这样静态variables仍然可以以某种方式被引用(使用debugging或者符号选项)。 在这两种情况下编译单元都不会受到影响,因为链接器首先parsing本地引用。
在“全球和静态”领域:)
在C ++中有几个存储区域
- 堆
- 免费店铺
- 堆
- 全球和静态
- 常量
在这里看到你的问题的详细答案
如前所述,存储在数据段或代码段中的静态variables。
你可以确定它不会被分配在堆栈或堆上。
由于static
关键字定义variables的范围是一个文件或者函数,所以碰撞没有风险。在碰撞的情况下有一个编译器/链接器来警告你。
一个很好的例子
那么这个问题有点太老了,但是因为没有人指出任何有用的信息:检查'mohit12379'的post解释在符号表中存储具有相同名称的静态variables: http : //www.geekinterview.com/question_details/ 24745
我试过用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
所以,这就是说,你的四个variables位于数据段事件中,但是具有不同的偏移量。
答案很可能取决于编译器,所以你可能想编辑你的问题(我的意思是,即使段的概念是不是由ISO C或ISO C ++强制)。 例如,在Windows上,可执行文件不包含符号名称。 一个'foo'将偏移0x100,另一个可能是0x2B0,并且编译来自两个翻译单元的代码,知道“他们的”foo的偏移量。
他们都将被独立存储,但是如果你想让其他开发者清楚你可能想把它们包装在命名空间中。
您已经知道它是否存储在bss中(块以符号开始),也称为未初始化的数据段或已初始化的数据段。
我们举一个简单的例子
void main(void) { static int i; }
上面的静态variables没有被初始化,所以进入未初始化的数据段(bss)。
void main(void) { static int i=10; }
当然它初始化为10,所以它初始化数据段。