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

Ben*_*oit 169 c c++ compiler-construction

在可执行文件的哪个段(.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);
}                              }
Run Code Online (Sandbox Code Playgroud)

如果我编译两个文件并将其链接到重复调用fooTest()和barTest的main,则printf语句将独立增加.有意义,因为foo和bar变量是翻译单元的本地变量.

但是存储分配在哪里?

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

Don*_*eld 124

你的静力学去哪里取决于它们是否为零初始化.零初始化静态数据进入.BSS(由符号开始的块),非零初始化数据进入.DATA

  • 通过"非0初始化"你可能意味着"初始化,但使用0以外的东西".因为在C/C++中没有"非初始化"的静态数据.默认情况下,静态的所有内容都是零初始化的. (46认同)
  • @Don Neufeld:你的答案根本没有回答这个问题.我不明白为什么它被接受.因为'foo'和'bar'都是非0初始化的.问题是在.bss或.data中放置两个具有相同名称的静态/全局变量 (19认同)
  • @Andrey:无论如何,这就是我读这句话的方式. (4认同)

kar*_*arn 109

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

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

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

在此输入图像描述


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

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

  • @GabrielStaples +1,强调初始化数据可以进一步分为只读(=> .rodata 部分)和读写(=> .data 部分)。 (3认同)
  • 而不是这个(“**初始化数据段**:所有全局、静态和常量数据都存储在这里。**未初始化数据段(BSS)**:所有未初始化数据都存储在这个段中。”),我认为应该这样说:(“**初始化数据段**:所有初始化为非零值的全局变量和静态变量,以及所有常量数据,都存储在这里。**未初始化数据段(BSS)* *:所有未初始化或初始化为零的全局变量和静态变量都存储在该段中。”)。 (2认同)
  • 另请注意,据我了解,“初始化数据”可以由初始化的**变量**和* **常量**组成。在微控制器(例如:STM32)上,**初始化变量**默认存储在*闪存*内存中,并*在启动时复制到 RAM*,并且**初始化常量**保留在其中,旨在从中读取,*仅限闪存*,以及包含程序本身的*文本*,并保留在*仅限闪存中。* (2认同)
  • 链接已损坏:( (2认同)

小智 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
Run Code Online (Sandbox Code Playgroud)

本地范围可能意味着转换单元(源文件),函数或块的本地,取决于其定义的位置.要使变量对多个函数可见,它必须位于DATA或BSS区域(取决于它是否分别显式初始化).然后根据源文件中的所有函数或函数确定其范围.


Seb*_*ose 21

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

但是,静态的含义是"内部联系".因此,符号在编译单元内部(foo.c,bar.c),不能在编译单元外引用.所以,没有名称冲突.

  • ugasoft:函数外部的静态是连接修饰符,里面是存储修饰符,其中可以没有碰撞开始. (2认同)

pax*_*blo 13

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

在函数内部使用static是不同的 - 变量只对函数可见,它的值只是在对该函数的调用中保留.

实际上,静态根据它的位置做两件事.但是,在其他情况下,它会限制变量的可见性以防止命名空间冲突,

话虽如此,我相信它会存储在DATA中,而DATA往往具​​有初始化变量.BSS最初代表byte-set- <something>,它包含未初始化的变量.

  • @paxdiablo:您提到了两种类型的静态变量。这篇文章 (http://en.wikipedia.org/wiki/Data_segment ) 指的是哪一个?数据段还包含全局变量(本质上与静态变量完全相反)。`那么,一段内存(数据段)如何存储可以从任何地方访问的变量(全局变量)以及那些具有有限范围(静态变量的情况下的文件范围或函数范围)的变量? (2认同)

uga*_*oft 10

在"全球和静态"领域:)

C++中有几个内存区域

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

请看这里详细解答您的问题


Cir*_*四事件 10

如何自己找到它 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;
}
Run Code Online (Sandbox Code Playgroud)

编译:

gcc -ggdb -c main.c
Run Code Online (Sandbox Code Playgroud)

用以下代码反编译代码:

objdump -Sr main.o
Run Code Online (Sandbox Code Playgroud)
  • -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
Run Code Online (Sandbox Code Playgroud)

.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


You*_*oub 10

这是如何(易于理解):

堆栈、堆和静态数据


小智 6

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


itj*_*itj 5

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

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

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