Logo
Section 1 / 7

局部作用域与全局作用域

了解局部作用域和全局作用域,并初步认识可执行文件的结构。

导入

局部作用域与全局作用域

了解局部作用域和全局作用域,并初步认识可执行文件的结构。

作用域

变量的作用域是指变量在代码中可见并可使用的区域。

作用域有时也被称为变量的生命周期。当变量超出其作用域时,它们会被销毁。我们很快就会深入探讨局部变量销毁的具体机制!

...
程序与变量作用域对比

主要有两种作用域:

  • 局部作用域
  • 全局作用域

全局作用域

全局作用域是整个程序的作用域。

在全局作用域中声明的变量:

  • 称为全局变量。
  • 在整个程序中都可见或可访问。
  • 变量的生命周期贯穿程序运行的全过程。

以下代码声明了两个全局变量——global1 和 global2。

1#include <stdio.h> 2 3// global1 和 global2 在程序中任何地方都可见 4int global1 = 100; 5float global2 = 3.3f; 6 7void otherFunc() 8{ 9 // otherFunc 可以访问这些变量 10 printf("[otherFunc] %d %f\n", global1, global2); 11} 12 13int main() 14{ 15 // main 函数也可以访问这些变量 16 printf("[main] %d %f\n", global1, global2); 17 18 otherFunc(); 19 return 0; 20}

代码编译后的结果是一个可执行文件。执行下面的代码块,它将编译代码,然后运行 ls 命令,显示生成的可执行文件(main)。

1#include <stdio.h> 2#include <stdlib.h> 3 4int main() 5{ 6 system("ls"); // 显示 "output" 目录的内容 7 system("file main"); // 显示 main 是一个二进制文件 8 return 0; 9}

输出可能如下所示:

1__ed_mem.txt 2__ed_script.sh 3__ed_stderr.txt 4__ed_stdout.txt 5main 6main.c 7output 8main: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, not stripped

注意,main.c 是用于生成可执行文件 main 的源文件。最后一行表明 main 确实是一个可执行文件。

可执行文件包含多个段,每个段存储不同类型的信息:

  • .code 或 .text 段存储我们编写的代码。
  • .rodata(只读数据)段存储常量变量。
  • .bss 段存储未初始化的全局变量(包括静态全局变量)。
  • .data 段存储所有已初始化的全局变量(包括静态全局变量)。

全局变量存储在 .bss 和 .data 段中。来自 .bss 段的未初始化全局或静态变量,在程序加载并开始运行时会自动被初始化为 0。

让我们通过下面的代码来探索不同变量是如何存储的。

我们可以使用 Linux 的 size 命令来查看各段的大小。可以通过 system 函数从 C 代码中调用该命令。

先运行一次不声明任何变量的代码。你的输出可能会有所不同,因此建议打开记事本或其他编辑器临时保存。注意,在本课程中我们省略了最后两列,因为它们并不重要。

1#include <stdio.h> 2#include <stdlib.h> 3 4// int global1 = 5; 5// int global2 = 11; 6// int global3; 7// int global4; 8 9int main() 10{ 11 system("size main"); 12 return 0; 13}

我们的基准值为:

1 text data bss dec 2 1186 560 8 1754

取消注释第 4 行和第 5 行,再次运行代码。我们声明了两个已初始化的全局变量。

1 text data bss dec 2 1186 568 8 1762

观察 .data 列,它从 560 变为 568,这是因为这两个全局变量被放入了 .data 段。

重新注释第 4 行和第 5 行,取消注释第 6 行和第 7 行。与基准值进行比较:

1 text data bss dec 2 1186 560 16 1762

.bss 列从 8 变为 16,因为两个未初始化的全局变量被存放在该段中。

你可以在代码中自由添加更多变量,进一步探索。

局部作用域

局部作用域是函数或代码块的作用域。

在局部作用域中声明的变量:

  • 称为局部变量。
  • 仅在声明它的函数或代码块内部可见或可访问。
  • 变量的生命周期与函数的执行周期一致。

以下代码声明了三个局部变量——local1、local2 和 local3。

1#include <stdio.h> 2 3void otherFunc() 4{ 5 // otherFunc 可以看到 local2,因为 local2 定义在 otherFunc 中(第 7 行) 6 // otherFunc 无法看到 local1 和 local3 7 int local2 = 15; 8 printf("[otherFunc] %d\n", local2); 9} 10 11int main() 12{ 13 // main 可以看到 local1,因为 local1 在 main 中定义(第 12 行) 14 // main 无法看到 local2 15 int local1 = 14; 16 printf("[main] %d\n", local1); 17 18 //{ } 创建一个代码块,限制其中变量的可见性仅限于该块 19 { 20 int local3 = 11; 21 printf("[main 内部的代码块] %d\n", local3); 22 23 // } 之后,local3 就不再可见 24 } 25 26 // local3 在此处不可见,取消下面这行注释会导致编译错误 27 // printf("[代码块之后] %d\n", local3); 28 29 otherFunc(); 30 return 0; 31}

前文提到的段(.code、.data、.bss、.rodata)被称为静态段,因为它们存储在可执行文件中,且大小在编译时已知。

另一方面,我们还有栈和堆。这两个是动态分配的内存区域,其大小在运行时才确定。

栈用于存储局部变量。例如,上述代码中的 local1、local2 和 local3 会被编译器放置到栈段中。

...
磁盘上的程序与内存中的程序对比

我们将在后续课程中深入探讨栈和堆。
本节课结束后,请记住:局部变量存储在栈中。

简介

栈的定义