静态变量存储在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格式的文件。 因此,我认为在可执行文件中必须为这些静态变量保留一些空间。
出于讨论目的,我们假设我们使用GCC工具链。

你的静力学取决于它们是否初始化为0。 0初始化的静态数据进入.BSS(由符号开始的块) ,非0初始化数据进入.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中,而这些数据往往具有初始化变量。 BSS最初代表byte-set- ,它包含未初始化的变量。

如何使用objdump -Sr自己找到它

要真正了解正在发生的事情,您必须了解链接器重定位。 如果你从未接触到这一点,请考虑先阅读这篇文章 。

让我们分析一下Linux x86-64 ELF示例来亲自看看:

 #include  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  6: R_X86_64_PC32 .data-0x4 

并且.data-0x4表示它将转到.data段的第一个字节。

-0x4是因为我们使用RIP相对寻址,因此指令中的%ripR_X86_64_PC32

这是必需的,因为RIP指向以下指令,该指令在00 00 00 00之后开始4个字节,这将被重新定位。 我在以下url详细解释了这一点: https : //stackoverflow.com/a/30515926/895245

然后,如果我们将源修改为i = 1并进行相同的分析,我们得出结论:

  • static int i = 0继续.bss
  • static int i = 1继续.data

这取决于您正在使用的平台和编译器。 一些编译器直接存储在代码段中。 静态变量始终只能由当前转换单元访问,并且不会导出名称,因此不会发生名称冲突的原因。

在“全球和静态”领域:)

C ++中有几个内存区域

  • 免费商店
  • 全球和静态
  • 常量

请看这里详细解答您的问题

在编译单元中声明的数据将进入.BSS或该文件输出的.Data。 BSS中的初始化数据,未在DATA中初始化。

静态数据和全局数据之间的区别在于在文件中包含符号信息。 编译器倾向于包括符号信息,但仅标记全局信息。

链接器尊重此信息。 静态变量的符号信息被丢弃或损坏,因此仍可以某种方式引用静态变量(使用调试或符号选项)。 在任何情况下,编译器单元都不会受到影响,因为链接器首先解析本地引用。

如前所述,存储在数据段或代码段中的静态变量。
您可以确定它不会在堆栈或堆上分配。
不存在冲突风险,因为static关键字将变量的范围定义为文件或函数,如果发生冲突,则会有编译器/链接器警告您。
一个很好的例子

那么这个问题太旧了,但是因为没有人指出任何有用的信息:通过’mohit12379’检查post,解释符号表中具有相同名称的静态变量的存储: 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  0x0000000000400537 <+10>: add $0x1,%eax 0x000000000040053a <+13>: mov %eax,0x200b00(%rip) # 0x601040  0x0000000000400540 <+19>: mov 0x200afe(%rip),%eax # 0x601044  0x0000000000400546 <+25>: add $0x1,%eax 0x0000000000400549 <+28>: mov %eax,0x200af5(%rip) # 0x601044  0x000000000040054f <+34>: mov 0x200aef(%rip),%edx # 0x601044  0x0000000000400555 <+40>: mov 0x200ae5(%rip),%eax # 0x601040  0x000000000040055b <+46>: mov %eax,%esi 0x000000000040055d <+48>: mov $0x400654,%edi 0x0000000000400562 <+53>: mov $0x0,%eax 0x0000000000400567 <+58>: callq 0x400410  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  0x0000000000400578 <+10>: add $0x1,%eax 0x000000000040057b <+13>: mov %eax,0x200ac7(%rip) # 0x601048  0x0000000000400581 <+19>: mov 0x200ac5(%rip),%eax # 0x60104c  0x0000000000400587 <+25>: add $0x1,%eax 0x000000000040058a <+28>: mov %eax,0x200abc(%rip) # 0x60104c  0x0000000000400590 <+34>: mov 0x200ab6(%rip),%edx # 0x60104c  0x0000000000400596 <+40>: mov 0x200aac(%rip),%eax # 0x601048  0x000000000040059c <+46>: mov %eax,%esi 0x000000000040059e <+48>: mov $0x40065c,%edi 0x00000000004005a3 <+53>: mov $0x0,%eax 0x00000000004005a8 <+58>: callq 0x400410  0x00000000004005ad <+63>: pop %rbp 0x00000000004005ae <+64>: retq End of assembler dump. 

这是objdump的结果

 Disassembly of section .data: 0000000000601030 <__data_start>: ... 0000000000601038 <__dso_handle>: ... 0000000000601040 : 601040: 01 00 add %eax,(%rax) ... 0000000000601044 : 601044: 02 00 add (%rax),%al ... 0000000000601048 : 601048: 0a 00 or (%rax),%al ... 000000000060104c : 60104c: 14 00 adc $0x0,%al 

所以,也就是说,你的四个变量位于数据部分事件中的名称相同,但具有不同的偏移量。

答案可能很大程度上取决于编译器,因此您可能想要编辑您的问题(我的意思是,甚至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初始化,因此它进入初始化数据段。