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

在哪个段(。BSS, .DATA,其他)的可执行文件的静态变量存储,以便他们没有名称冲突? 例如:< / p >


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工具链
223056 次浏览

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

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

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

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

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

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

实际上,静态的作用取决于它所处的位置。然而,在这两个情况下,变量可见性被限制在这样一种方式,你可以很容易地防止链接时的名称空间冲突。

话虽如此,我相信它将存储在DATA节中,该节倾向于将变量初始化为非零的值。当然,这是一个实现细节,而不是标准强制要求的东西——它只关心的行为,,而不是事情在幕后是如何完成的。

它们都将被独立存储,但是如果您想让其他开发人员清楚地知道,您可能希望将它们包装在名称空间中。

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

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.

静态变量存储在数据段或代码段,如前所述 你可以确定它不会被分配到堆栈或堆上 没有冲突风险,因为static关键字定义了变量的作用域为文件或函数,如果发生冲突,编译器/链接器会警告你 一个漂亮的例子

静态数据的位置取决于它们是否为zero-initializedzero-initialized静态数据放入.BSS(由符号开始的块)non-zero-initialized数据放入. data

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

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

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

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

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

好吧,这个问题有点太老了,但既然没有人指出任何有用的信息: 查看'mohit12379'的帖子,解释了在符号表中存储同名静态变量的方法: http://www.geekinterview.com/question_details/24745 < / p >

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

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

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

enter image description here

这里有一个很好的链接解释这些概念:C语言中的内存管理:堆和堆栈

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

让我们举一个简单的例子

void main(void)
{
static int i;
}

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

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

当然它初始化了10,所以它去初始化数据段。

我尝试了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

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

如何用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相对寻址,因此指令中的%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

这是如何(容易理解):

堆栈,堆和静态数据 .