C和C ++中的静态变量存储在哪里?


180

静态变量存储在可执行文件的哪个段(.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的主文件,则printf语句将独立增加。这是有道理的,因为foo和bar变量是转换单元的本地变量。

但是在哪里分配存储空间?

明确地说,假设您拥有一个可以以ELF格式输出文件的工具链。因此,我相信,有将一些空间,对于那些静态变量的可执行文件保留。
出于讨论目的,假设我们使用GCC工具链。


1
大多数人告诉您应该将它们存储在.DATA部分中,而不是回答您的问题:.DATA部分中的确切位置以及如何找到位置。我看到您已经标记了答案,所以您已经知道如何找到答案了?
lukmac 2011年

为什么要初始化和未初始化分别放在不同的部分:linuxjournal.com/article/1059
mhk 2012年

1
在运行时分配给全局/静态变量的存储与它们的名称解析无关,后者在构建/链接时发生。可执行文件生成后-没有更多名称。
valdo 2014年

2
这个问题毫无意义,它建立在一个错误的前提上,即未导出符号的“名称冲突”是可以存在的。没有合法问题的事实可能解释了某些答案的可怕程度。很难相信很少有人能做到这一点。
underscore_d

Answers:


131

静态变量的去向取决于它们是否被零初始化零初始化的静态数据进入.BSS(以符号开头的块)非零初始化的数据进入.DATA


50
“非0初始化”可能意味着“已初始化,但非0”。因为在C / C ++中没有“未初始化的”静态数据之类的东西。静态的所有内容默认情况下都是零初始化的。
AnT 2010年

21
@Don Neufeld:您的答案根本无法回答问题。我不明白为什么要接受它。因为'foo'和'bar'都未初始化为0。问题是在哪里将两个具有相同名称的静态/全局变量放在.bss或.data中
lukmac 2011年

我使用的实现是将显式初始化为零的.data静态数据传入,而没有初始化程序的静态数据传入.bss
MM

1
@MM在我的情况下,静态成员是未初始化(隐式初始化为0)还是显式初始化为0,在两种情况下,它都添加到.bss节中。
cbinder

此信息是否特定于某种可执行文件类型?我假设,由于您未指定,所以它至少适用于ELF和Windows PE可执行文件,但是其他类型呢?
杰里·耶利米

116

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

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

这是解释此概念的图:

在此处输入图片说明


这里是一个很好的链接,解释了这些概念:

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


上面的答案说0初始化进入BSS。0初始化意味着未初始化还是0本身?如果它本身表示0,那么我认为您应该在答案中包括它。
维拉伊,2015年

常量数据不存储在文本部分的.data段中,而是存储在.const段中。
user10678

代替这个(“ 初始化数据段:所有全局,静态和常量数据都存储在这里。未初始化数据段(BSS):所有未初始化数据都存储在该段中。”),我认为应该这样说:(“ 初始化数据段:所有初始化为非零值的全局和静态变量以及所有常量数据都存储在此处未初始化数据段(BSS):所有初始化或未初始化的全局和静态变量到零,则存储在此段中。”)
加布里埃尔·斯台普斯

还要注意,据我所知,“初始化数据”可以由初始化变量 常量组成。在微控制器(例如STM32)上,默认情况下,已初始化的变量存储在Flash存储器中,并在启动时复制到RAM中,并且已初始化的常数Flash一起留在Flash中,并打算从中读取,而Text则包含程序本身,仅保留在Flash中。
加布里埃尔·斯台普斯

因此,我从该图中收集的信息是全局或静态变量(因为静态变量在持续时间内的作用类似于全局变量)既不在堆上也不在堆栈上,而是在这两个变量之外分配给内存。那正确吗?我想我可以再次查看STM32链接程序脚本,以进一步研究内存分配。
加布里埃尔·斯台普斯

32

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

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


21

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

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


没有。静态keyworld具有重载的含义:在这种情况下,static是存储修饰符,而不是链接修饰符。
ugasoft

4
ugasoft:函数外部的静态变量是链接修饰符,内部的是存储修饰符,在开始时没有碰撞。
wnoise

12

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

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

  • 免费商店
  • 全局和静态
  • const

请参阅此处,以详细了解您的问题:

以下总结了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.

12

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

在内部使用静态函数的方式有所不同-变量仅对函数可见(无论是否为静态),只是变量的值在对该函数的调用之间得以保留。

实际上,static根据其所在位置执行两项不同的操作。但是,在这两种情况下,变量的可见性都受到限制,您可以在链接时轻松防止名称空间冲突。

话虽如此,我相信它将被存储在该DATA节中,该节中的变量往往被初始化为非零值。当然,这是一个实现细节,不是标准所要求的东西–它只关心行为,而不关心幕后的工作方式。


1
@paxdiablo:您提到了两种类型的静态变量。本文(en.wikipedia.org/wiki/Data_segment)指的是哪一个?数据段还包含全局变量(本质上与静态变量完全相反)。So, how does a segment of memory (Data Segment) store variables that can be accessed from everywhere (global variables) and also those which have limited scope (file scope or function scope in case of static variables)?
Lazer

@eSKay,这与可见性有关。某个段中可能存储了某些内容,这些内容对于编译单元而言是本地的,而其他则可以完全访问。一个例子:考虑每个comp-unit对DATA段贡献一个块。它知道该块中的所有内容。它还在块中发布希望其他组合单元访问的那些地址的地址。链接器可以在链接时解析这些地址。
paxdiablo

11

如何用自己找到它 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


6

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


5

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

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

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


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

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


不仅如此。甚至现有的答案也不完整。只是提一下其他事情:线程局部变量。
Adriano Repetti 2014年

2

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



1

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


0

它们都将被独立存储,但是,如果您想让其他开发人员清楚,则可能需要将它们包装在命名空间中。


-1

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

让我们举一个简单的例子

void main(void)
{
static int i;
}

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

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

当然,它会初始化为10,因此会转到已初始化的数据段。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.