静态变量存储在C和C ++中的哪个位置?

时间:2008-09-18 14:29:06

标签: c++ c compiler-construction

可执行文件的哪个段(.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工具链。

16 个答案:

答案 0 :(得分:117)

你的静力学取决于它们是否零初始化零初始化静态数据进入.BSS (Block Started by Symbol),非零初始化数据进入.DATA

答案 1 :(得分:101)

当程序加载到内存中时,它被组织成不同的段。其中一个分段是 DATA分段。数据部分进一步细分为两部分:

初始化数据段:所有全局,静态和常量数据都存储在此处 未初始化的数据段(BSS):所有未初始化的数据都存储在此段中。

这是一个解释这个概念的图:

enter image description here

这是解释这些概念的非常好的链接:

  

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

答案 2 :(得分:31)

实际上,变量是元组(存储,范围,类型,地址,值):

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区域(取决于它是否分别显式初始化)。然后根据源文件中的所有函数或函数确定其范围。

答案 3 :(得分:21)

数据的存储位置取决于实现。

然而,静态的含义是“内部联动”。因此,符号是 internal 到编译单元(foo.c,bar.c),并且不能在编译单元之外引用。因此,不会发生名称冲突。

答案 4 :(得分:13)

我不相信会有碰撞。在文件级别使用static(外部函数)将变量标记为当前编译单元(文件)的本地变量。它在当前文件之外永远不可见,所以永远不必有名字。

在函数内部使用static是不同的 - 变量只对函数可见,它的值只是在对该函数的调用中保留。

实际上,静态根据它的位置做两件事。但是,在其他情况下,它会限制变量的可见性以防止命名空间冲突,

话虽如此,我相信它会存储在往往具有初始化变量的DATA中。 BSS最初代表字节集 - < something>其中包含未初始化的变量。

答案 5 :(得分:9)

如何使用objdump -Sr

自行查找

要真正理解发生了什么,您必须了解链接器重定位。如果您从未接触过,请考虑reading this post first

让我们自己分析一下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

答案 6 :(得分:8)

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

C ++中有几个内存区域

  • 免费商店
  • 全球&amp;静态
  • 常量

有关您问题的详细解答,请参阅here

答案 7 :(得分:6)

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

答案 8 :(得分:5)

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

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

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

答案 9 :(得分:2)

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

答案 10 :(得分:2)

这个问题太旧了,但是因为没有人指出任何有用的信息: 通过'mohit12379'检查帖子,解释符号表中具有相同名称的静态变量的存储: http://www.geekinterview.com/question_details/24745

答案 11 :(得分:2)

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

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

答案 12 :(得分:2)

这是(易于理解):

stack, heap and static data

答案 13 :(得分:1)

答案可能很大程度上取决于编译器,因此您可能想要编辑您的问题(我的意思是,甚至ISO C和ISO C ++都没有规定段的概念)。例如,在Windows上,可执行文件不带符号名称。一个'foo'将偏移0x100,另一个可能是0x2B0,并且两个转换单元的代码都是在知道“他们的”foo的偏移量的情况下编译的。

答案 14 :(得分:0)

它们都将独立存储,但是如果你想让其他开发人员明白你可能想要将它们包装在命名空间中。

答案 15 :(得分:-1)

你已经知道它存储在bss中(按符号开始的块)也称为未初始化的数据段或初始化的数据段。

让我们举一个简单的例子

void main(void)
{
static int i;
}

上面的静态变量未初始化,因此它转到未初始化的数据段(bss)。

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

当然它由10初始化,因此它进入初始化数据段。