当我们声明静态变量时,编译器实际上做了啥?

Posted

技术标签:

【中文标题】当我们声明静态变量时,编译器实际上做了啥?【英文标题】:What actually compiler does when we declare static variables?当我们声明静态变量时,编译器实际上做了什么? 【发布时间】:2015-08-15 09:29:10 【问题描述】:

我想知道引擎盖下到底发生了什么,编译器如何处理静态变量。与自动变量不同,静态变量的值即使在块结束后仍然存在,但编译器实际上如何处理呢?

【问题讨论】:

您的意思是,计算机中可能存在什么样的内存,其值超出了块的范围? static 变量只是一个范围有限的全局变量。 另外,您可以编写一些测试代码并自己检查map文件 @KerrekSB 是的,我的意思是,想知道编译器如何处理这一切? 推荐阅读:Data Segment和BSS Segment 【参考方案1】:

static 变量是作用域有限的全局变量。 @user3386109

    static/全局变量存在于程序的生命周期中。

    static/global 在程序启动时被初始化为:

    A.如果没有显式初始化:到位模式0。 B. 否则为double x = 1.23;等显式值

    static 变量范围要么限制为

    A.如果在函数外定义:文件范围,则只有文件内的代码才能“看到”变量。 B. 如果在函数内部定义:块范围:只有块内的代码可以“看到”变量。

    在其作用域内只有一个 static 变量实例,除非较低的作用域定义了另一个具有相同名称的实例。编译器首先使用最近的范围“知道”要访问哪个相同的命名变量。即使在函数内部,也不会重新创建或重新初始化它。

注意:对于多线程,其他注意事项也适用 - 未显示。

static int fred = 11;
int sally = 21;

void foo2(void) 
  static int fred = 31;
  int sally = 41;
  printf("static %d non-static %d\n", fred++, sally++);
  
    printf("static %d non-static %d\n", fred++, sally++);
    
      static int fred = 51;
      int sally = 61;
      printf("static %d non-static %d\n", fred++, sally++);
    
  


int main(void) 
  printf("static %d non-static %d\n", fred++, sally++);
  foo2();
  printf("static %d non-static %d\n", fred++, sally++);
  foo2();
  return 0;

输出

static 11 non-static 21
static 31 non-static 41
static 32 non-static 42
static 51 non-static 61
static 12 non-static 22
static 33 non-static 41
static 34 non-static 42
static 52 non-static 61

【讨论】:

【参考方案2】:

与堆栈中的局部变量不同,静态变量保存在特殊的数据段中。您的静态变量进入哪个段取决于它们是否初始化为 0。 .BSS (Block Started by Symbol)中有0个初始化静态数据,.DATA中有非0个初始化数据。

如果您想了解更多关于可执行文件中不同段的信息,this ***条目是一个很好的起点。我还强烈推荐 Randal E. Bryant 和 David R. O'Hallaron 的计算机系统:程序员的视角中的第 7 章。

我在这里描述一个特定的场景。您需要考虑到细节会因一种架构而异,从一种操作系统到另一种,等等。但是,可执行文件的一般布局仍如所述。真是令人兴奋的东西!

编辑:

作者请我澄清一下:

将 0 初始化变量划分为 .bss 和 非 0 初始化为 .data?

来自计算机系统:程序员的视角.BSS部分的第 7.4 节:

此部分在目标文件中不占用实际空间;它只是 占位符。对象文件格式区分初始化 和用于空间效率的未初始化变量:未初始化 变量不必占用对象中的任何实际磁盘空间 文件。

还有,来自Wikipedia:

通常只存储 .BSS 部分的长度,但不存储数据 在目标文件中。程序加载器分配和初始化 bss 部分在加载程序时的内存。

总结一下:这是一种节省内存的机制。

【讨论】:

很明显,我们可以在运行时更改静态变量的值,那么将初始化变量划分为 .bss 并未初始化为 .data 的意义何在?因为它们都是读写内存段。 请参考我在答案中的最新编辑。希望这可以为您澄清。 请记住,non 0 initializeduninitialized 是两个不同的概念! C 特别关注未初始化的静态变量:***.com/questions/1597405/…【参考方案3】:

这段代码:

void function()

    static int var = 6;

    // Make something with this variable
    var++;

内部类似:

int only_the_compiler_knows_this_actual_name = 6;

void function()

    // Make something with the variable
    only_the_compiler_knows_this_actual_name++;

换句话说,它是一种“全局”变量,但其名称不会与任何其他全局变量冲突。

【讨论】:

【参考方案4】:

典型的 C 编译器生成的汇编输出会创建四个内存“部分”。链接器/加载器通常在将程序加载到内存时将标有相同部分的各种项目组合在一起。最常见的部分是:

“text”:这是实际的程序代码。它被认为是只读的(例如,某些机器上的链接器/加载器可能会将其放在 ROM 中)。

“数据”:这只是一个分配的 RAM 区域,初始值是从可执行文件中复制的。加载器将分配内存,然后复制其初始内容。

“bss”:与数据相同,但初始化为零。

“stack”:由加载器为其程序堆栈分配。

全局变量和静态变量放在“data”和“bss”中,因此具有程序生命周期的生命周期。然而,静态变量不会将它们的名称放在符号表中,因此它们不能像全局变量那样在外部链接。变量的可见性和生命周期是完全不同的概念:C 的语法混淆了两者。

“自动”变量通常在程序执行期间分配在堆栈上(尽管如果它们非常大,它们可能会被分配在堆上)。它们只存在于它们的堆栈框架中。

【讨论】:

以上是关于当我们声明静态变量时,编译器实际上做了啥?的主要内容,如果未能解决你的问题,请参考以下文章

JVM方法的动态与静态绑定机制

静态成员

对于静态变量和动态变量

面试八股文

如何在机器级别或内存级别在编译器中实现变量范围

Golang笔记