免责声明:
- 这可能是一个非常微不足道的问题(尽管我找不到答案),并且
- 一个纯粹的理论问题(我从不需要这样做,也从未见过使用这种结构的代码,但我只是好奇如何/为什么会以这种方式发生。
C/C++
双标签,因为我在 C 和 C++ 上测试了这段代码,它只有 4 行代码(唯一的区别是gcc/clang
发出警告,而g++/clang++
给出错误。
背景:在回答另一个问题时,我开始思考为什么OP不能修改public static
变量。我想了一下,然后进一步减少了问题,在那里我可以看到相同的效果,但不需要任何类或静态成员变量。
问:那么下面的代码可以重现观察结果。
int global_n; // I know it can be initialized right away here also: int global_n = 1;
global_n = 2; // This does not compile in C++. In C it gives a warning about missing type-specifier
int main() {
global_n = 2; // This does compile in both C/C++ of course
}
这让我想到了我的问题:全局变量(因此
static
变量/成员变量)只能在声明时直接初始化。但任何后续修改都只能在函数内部进行。正确?这有什么具体原因吗?
在函数之外,你不能有语句(即可执行的代码行),只有声明和定义。
在全局范围内global_n = 2;
的情况下,C90 具有一个遗留功能,即如果声明变量时没有类型,则默认类型为int
(C99 删除了该功能并需要一个类型)。 这就是在这种情况下发生的情况,这也是您收到有关缺少类型的警告的原因。
C++没有该规则,因此这显示为函数外部的语句,这是一个错误。
简单的答案是语法不允许在复合语句之外执行代码{...}
,故事结束。
但是如果深入挖掘一点,C 也不允许这样的事情
// file scope
int x = 0;
int y = x;
C 也不允许这样做:
// file scope
int x = func();
原因是文件范围变量和声明为static
的变量都具有静态存储持续时间。这些变量实际上并没有在声明它们的行上初始化,而是更早,甚至在调用main()之前。(这也适用于具有静态存储持续时间C++对象。
在调用 main() 之前总是会执行启动代码,即使您没有看到它。这通常称为"C 运行时"或"CRT"。它的一部分工作是在调用main()之前初始化所有具有静态存储持续时间的变量/对象。
因此,如果您有此应用程序代码:
void foo (void)
{
static int var = 1;
printf("%d", var);
}
int main (void)
{
foo();
}
然后,在main
之前执行的代码将类似于以下简化的伪代码:
void startup (void) // point of entry when executable starts
{
set memory of "var" to 1
main();
}
这实际上是我们可以多次调用foo
而无需重新初始化var
的原因。行static int var = 1;
实际上并没有在源中的位置执行,而是更早地执行,而且只执行一次。与局部变量不同,局部变量通常在代码中与声明相同的位置初始化。
"CRT"初始化大致分为三个部分:
.data
初始化,它将程序员显式初始化的所有静态存储持续时间变量设置为值,如我示例中var
。.bss
初始化,将所有变量设置为零,程序员要么初始化为零,要么根本不初始化。
通过- C++对象的初始值设定项初始化具有静态存储持续时间。
除了这些C++结构之外,在启动时不会调用任何应用程序代码,这就是为什么 C 中不允许使用像int x = func();
这样的代码。
这也是为什么C++具有静态存储持续时间的对象不应该依赖于彼此的初始化值的原因:源中的声明顺序不一定与"CRT"中的初始化顺序相对应。
我的回答是关于C++的。C 可能不同。
全局变量(以及静态变量/成员变量)只能在声明时直接初始化。
没错,只能在声明中提供初始化器。但是,可以在没有初始化器的情况下提供声明。例:
extern int global_n; // only declaration; no definition; no initialiser
int global_n = 42; // re-declaration; definition; initialiser
但任何后续修改都只能在函数内部进行。正确?
不完全正确。可以在另一个全局变量的初始化器中修改全局变量:
int global_n1 = 666;
int global_n2 = global_n1 = 42;
在实践中,这可能是一个糟糕的设计选择 - 至少在这个简化的示例中;我想那里可能有实际的用例。
这有什么具体原因吗?
我想你的意思是,为什么你只能有声明语句,而不能有函数之外的其他类型的语句?
这只是语言的设计选择。C++程序是链接在一起的独立单元。当语句来自单独的源文件时,应按什么顺序执行?它们与静态对象初始化相关的执行情况如何?静态初始化的当前状态已经足够复杂了;我认为不允许在命名空间范围内使用表达式语句是一个不错的选择。