局部作用域与全局作用域
了解局部作用域和全局作用域,并初步认识可执行文件的结构。
作用域
变量的作用域是指变量在代码中可见并可使用的区域。
作用域有时也被称为变量的生命周期。当变量超出其作用域时,它们会被销毁。我们很快就会深入探讨局部变量销毁的具体机制!
主要有两种作用域:
- 局部作用域
- 全局作用域
全局作用域
全局作用域是整个程序的作用域。
在全局作用域中声明的变量:
- 称为全局变量。
- 在整个程序中都可见或可访问。
- 变量的生命周期贯穿程序运行的全过程。
以下代码声明了两个全局变量——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 会被编译器放置到栈段中。
我们将在后续课程中深入探讨栈和堆。
本节课结束后,请记住:局部变量存储在栈中。