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

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):所有未初始化的数据都存储在此段中。

下面是一个图表来解释这个概念:

enter image description here

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

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显示搬迁信息

f的反编译过程中,我们看到:

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

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

因为我们使用的是RIP相对寻址,所以存在-0x4,因此指令中的%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


在"全局和静态"区域中:)

C++中有几个内存区域

  • 免费商店
  • 堆栈
  • 全局和静态
  • 康斯特

请参阅此处了解您的问题的详细答案


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


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

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

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


这是如何(容易理解):

stack, heap and static data


这个问题有点太老了,但由于没有人指出任何有用的信息:通过"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

所以,也就是说,四个变量位于数据节事件中,名称相同,但偏移量不同。


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


答案很可能取决于编译器,所以你可能想编辑你的问题(我的意思是,即使段的概念不是由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初始化的,所以它进入初始化的数据段。