静态变量存储在可执行文件的哪个段(.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 工具链。

有帮助吗?

解决方案

你的静力学去向取决于它们是否存在 零初始化 或不。 零初始化 静态数据进入 .BSS(以符号开头的块), 非 零初始化 数据进入 。数据

其他提示

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

初始化数据段: 所有全局、静态和常量数据都存储在这里。
未初始化数据段(BSS): 所有未初始化的数据都存储在该段中。

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

enter image description here


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

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 中,而 DATA 往往已初始化变量。BSS 最初代表 byte-set-<something>,它保存未初始化的变量。

如何自己找到它 objdump -Sr

要真正了解发生了什么,您必须了解链接器重定位。如果您从未接触过,请考虑 首先阅读这篇文章.

我们来分析一个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 相对寻址,因此 %rip 在指令和 R_X86_64_PC32.

这是必需的,因为 RIP 指向 下列的 指令,该指令在 4 个字节之后开始 00 00 00 00 这就是将要搬迁的地方。我在以下位置对此进行了更详细的解释: 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”的帖子,解释了符号表中同名静态变量的存储: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

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

方法如下(很容易理解):

stack, heap and static data

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top