203

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

4

16 回答 16

145

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

于 2008-09-18T15:07:35.283 回答
125

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

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

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

在此处输入图像描述

这是解释这些概念的非常好的链接:C中的内存管理:堆和堆栈

于 2012-07-28T05:08:36.263 回答
35

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

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

于 2008-09-20T19:38:30.473 回答
23

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

但是,静态的含义是“内部链接”。因此,该符号在编译单元(foo.c、bar.c)内部,不能在该编译单元之外引用。因此,不可能有名称冲突。

于 2008-09-18T14:33:38.453 回答
15

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

C++中有几个内存区域:

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

有关您的问题的详细答案,请参见此处:

下面总结了 C++ 程序的主要不同内存区域。请注意,某些名称(例如,“堆”)在草案 [标准] 中并未出现。

     Memory Area     Characteristics and Object Lifetimes
     --------------  ------------------------------------------------

     Const Data      The const data area stores string literals and
                     other data whose values are known at compile
                     time.  No objects of class type can exist in
                     this area.  All data in this area is available
                     during the entire lifetime of the program.

                     Further, all of this data is read-only, and the
                     results of trying to modify it are undefined.
                     This is in part because even the underlying
                     storage format is subject to arbitrary
                     optimization by the implementation.  For
                     example, a particular compiler may store string
                     literals in overlapping objects if it wants to.


     Stack           The stack stores automatic variables. Typically
                     allocation is much faster than for dynamic
                     storage (heap or free store) because a memory
                     allocation involves only pointer increment
                     rather than more complex management.  Objects
                     are constructed immediately after memory is
                     allocated and destroyed immediately before
                     memory is deallocated, so there is no
                     opportunity for programmers to directly
                     manipulate allocated but uninitialized stack
                     space (barring willful tampering using explicit
                     dtors and placement new).


     Free Store      The free store is one of the two dynamic memory
                     areas, allocated/freed by new/delete.  Object
                     lifetime can be less than the time the storage
                     is allocated; that is, free store objects can
                     have memory allocated without being immediately
                     initialized, and can be destroyed without the
                     memory being immediately deallocated.  During
                     the period when the storage is allocated but
                     outside the object's lifetime, the storage may
                     be accessed and manipulated through a void* but
                     none of the proto-object's nonstatic members or
                     member functions may be accessed, have their
                     addresses taken, or be otherwise manipulated.


     Heap            The heap is the other dynamic memory area,
                     allocated/freed by malloc/free and their
                     variants.  Note that while the default global
                     new and delete might be implemented in terms of
                     malloc and free by a particular compiler, the
                     heap is not the same as free store and memory
                     allocated in one area cannot be safely
                     deallocated in the other. Memory allocated from
                     the heap can be used for objects of class type
                     by placement-new construction and explicit
                     destruction.  If so used, the notes about free
                     store object lifetime apply similarly here.


     Global/Static   Global or static variables and objects have
                     their storage allocated at program startup, but
                     may not be initialized until after the program
                     has begun executing.  For instance, a static
                     variable in a function is initialized only the
                     first time program execution passes through its
                     definition.  The order of initialization of
                     global variables across translation units is not
                     defined, and special care is needed to manage
                     dependencies between global objects (including
                     class statics).  As always, uninitialized proto-
                     objects' storage may be accessed and manipulated
                     through a void* but no nonstatic members or
                     member functions may be used or referenced
                     outside the object's actual lifetime.
于 2008-09-18T14:43:35.310 回答
14

如何自己找到它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
于 2015-06-04T10:59:44.690 回答
13

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

在函数内部使用 static是不同的 - 变量仅对函数可见(无论是否静态),它只是在调用该函数时保留其值。

实际上,静态根据它的位置做了两件不同的事情。然而,在这两种情况下,变量的可见性都受到限制,因此您可以在链接时轻松防止命名空间冲突。

话虽如此,我相信它将存储在该DATA部分中,该部分往往具有初始化为非零值的变量。当然,这是一个实现细节,而不是标准规定的东西——它只关心行为,而不关心事情是如何在幕后完成的。

于 2008-09-18T14:35:36.310 回答
10

这是如何(容易理解):

堆栈、堆和静态数据

于 2018-03-12T11:24:52.410 回答
6

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

于 2008-09-18T14:33:42.010 回答
4

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

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

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

于 2008-09-18T20:27:31.567 回答
3

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

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

于 2014-10-13T17:43:41.033 回答
2

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

于 2008-09-18T15:00:20.620 回答
1

答案很可能取决于编译器,因此您可能想要编辑您的问题(我的意思是,即使是段的概念也不是 ISO C 或 ISO C++ 强制要求的)。例如,在 Windows 上,可执行文件不带有符号名称。一个“foo”的偏移量为 0x100,另一个可能为 0x2B0,两个翻译单元的代码在编译时都知道“它们的”foo 的偏移量。

于 2008-09-18T14:34:37.827 回答
1

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

于 2011-03-20T21:12:06.387 回答
0

它们都将独立存储,但是如果您想向其他开发人员说明,您可能希望将它们包装在命名空间中。

于 2008-09-18T14:38:53.323 回答
-1

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

让我们举一个简单的例子

void main(void)
{
static int i;
}

上面的静态变量没有初始化,所以它进入了未初始化的数据段(bss)。

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

当然它初始化了 10 所以它进入了初始化的数据段。

于 2014-06-30T07:28:31.803 回答