当我们声明静态变量时,编译器实际上做了什么?
What actually compiler does when we declare static variables?
我想知道幕后到底发生了什么,编译器如何处理静态变量。与自动变量不同,静态变量的值即使在块结束后仍然存在,但编译器实际上如何处理这个?
与进入堆栈的局部变量不同,静态变量保存在特殊的数据段中。您的静态变量转到哪个段取决于它们是否初始化为 0。 0 初始化静态数据进入 .BSS (Block Started by Symbol),非 0 初始化数据进入 .DATA.
如果您想了解有关可执行文件中不同段的更多信息,this 维基百科条目是一个很好的起点。我还强烈推荐 Randal E. Bryant 和 David R. O'Hallaron 着的 计算机系统:程序员的视角 中的第 7 章。
我在这里描述一个特定的场景。您需要考虑到细节会因一种架构而异,从一种 OS 到另一种,等等。但是,可执行文件的一般布局仍如所描述的那样。确实令人兴奋!
编辑:
作者请我澄清一下:
what is the point of dividing the 0 initialized variable to .bss and
non 0 initialized to .data?
摘自 计算机系统:程序员的视角 .BSS 部分的第 7.4 节:
This section occupies no actual space in the object file; it is merely
a place holder. Object file formats distinguish between initialized
and uninitialized variables for space efficiency: uninitialized
variables do not have to occupy any actual disk space in the object
file.
并且,来自 Wikipedia:
Typically only the length of the .BSS section, but no data, is stored
in the object file. The program loader allocates and initializes
memory for the bss section when it loads the program.
总结一下:这是一种节省内存的机制。
典型的 C 编译器生成的汇编输出会创建四个 "sections" 内存。 linker/loader 通常在将程序加载到内存中时将标有相同部分的各种项目组合在一起。最常见的部分是:
"text":这是实际的程序代码。它被认为是只读的(例如,linker/loader 在某些机器上可能会将其放在 ROM 中)。
"data":这只是分配的 RAM 区域,初始值从 executable 文件复制而来。加载程序将分配内存,然后复制其初始内容。
"bss":与数据相同,但初始化为零。
"stack": 简单地由加载程序为其程序堆栈分配。
全局变量和静态变量放在"data"和"bss"中,因此具有程序生命周期的生命周期。但是,静态变量不会将它们的名称放在符号 table 中,因此它们不能像全局变量一样从外部链接。变量的可见性和生命周期是完全不同的概念:C 的语法混淆了两者。
"Auto" 变量通常在程序执行期间分配在堆栈上(尽管如果它们非常大,它们可能会分配在堆上)。它们只存在于它们的堆栈框架中。
此代码:
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++;
}
换句话说,它是一种"global"变量,其名称不与任何其他全局变量冲突。
static
变量是范围有限的全局变量。 @user3386109
static
/全局变量在程序的生命周期内存在。
static
/global 在程序启动时被初始化为:
一个。如果没有显式初始化:到位模式 0
.
B. 否则为显式值,如 double x = 1.23;
static
变量范围要么限制在
一个。如果在函数外定义:文件范围,只有文件内的代码可以"see"变量。
B. 如果在函数内部定义:块范围:只有块内的代码可以 "see" 变量。
在其范围内只有一个 static
变量的实例,除非较低的范围定义了另一个具有相同名称的实例。编译器 "knows" 首先使用最近的作用域访问哪个同名变量。它不会重新创建或重新初始化,即使在函数内部也是如此。
注意:对于多线程,其他注意事项适用 - 未显示。
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
我想知道幕后到底发生了什么,编译器如何处理静态变量。与自动变量不同,静态变量的值即使在块结束后仍然存在,但编译器实际上如何处理这个?
与进入堆栈的局部变量不同,静态变量保存在特殊的数据段中。您的静态变量转到哪个段取决于它们是否初始化为 0。 0 初始化静态数据进入 .BSS (Block Started by Symbol),非 0 初始化数据进入 .DATA.
如果您想了解有关可执行文件中不同段的更多信息,this 维基百科条目是一个很好的起点。我还强烈推荐 Randal E. Bryant 和 David R. O'Hallaron 着的 计算机系统:程序员的视角 中的第 7 章。
我在这里描述一个特定的场景。您需要考虑到细节会因一种架构而异,从一种 OS 到另一种,等等。但是,可执行文件的一般布局仍如所描述的那样。确实令人兴奋!
编辑:
作者请我澄清一下:
what is the point of dividing the 0 initialized variable to .bss and non 0 initialized to .data?
摘自 计算机系统:程序员的视角 .BSS 部分的第 7.4 节:
This section occupies no actual space in the object file; it is merely a place holder. Object file formats distinguish between initialized and uninitialized variables for space efficiency: uninitialized variables do not have to occupy any actual disk space in the object file.
并且,来自 Wikipedia:
Typically only the length of the .BSS section, but no data, is stored in the object file. The program loader allocates and initializes memory for the bss section when it loads the program.
总结一下:这是一种节省内存的机制。
典型的 C 编译器生成的汇编输出会创建四个 "sections" 内存。 linker/loader 通常在将程序加载到内存中时将标有相同部分的各种项目组合在一起。最常见的部分是:
"text":这是实际的程序代码。它被认为是只读的(例如,linker/loader 在某些机器上可能会将其放在 ROM 中)。
"data":这只是分配的 RAM 区域,初始值从 executable 文件复制而来。加载程序将分配内存,然后复制其初始内容。
"bss":与数据相同,但初始化为零。
"stack": 简单地由加载程序为其程序堆栈分配。
全局变量和静态变量放在"data"和"bss"中,因此具有程序生命周期的生命周期。但是,静态变量不会将它们的名称放在符号 table 中,因此它们不能像全局变量一样从外部链接。变量的可见性和生命周期是完全不同的概念:C 的语法混淆了两者。
"Auto" 变量通常在程序执行期间分配在堆栈上(尽管如果它们非常大,它们可能会分配在堆上)。它们只存在于它们的堆栈框架中。
此代码:
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++;
}
换句话说,它是一种"global"变量,其名称不与任何其他全局变量冲突。
static
变量是范围有限的全局变量。 @user3386109
static
/全局变量在程序的生命周期内存在。static
/global 在程序启动时被初始化为:一个。如果没有显式初始化:到位模式
0
.
B. 否则为显式值,如double x = 1.23;
static
变量范围要么限制在一个。如果在函数外定义:文件范围,只有文件内的代码可以"see"变量。
B. 如果在函数内部定义:块范围:只有块内的代码可以 "see" 变量。在其范围内只有一个
static
变量的实例,除非较低的范围定义了另一个具有相同名称的实例。编译器 "knows" 首先使用最近的作用域访问哪个同名变量。它不会重新创建或重新初始化,即使在函数内部也是如此。
注意:对于多线程,其他注意事项适用 - 未显示。
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