免责声明:
C/C++
双标签,因为我测试了C和C ++上的代码位,它只有4行代码(唯一的区别是gcc/clang
发出警告,而g++/clang++
给出错误。)背景:在回复另一个question时,我开始思考为什么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中不允许使用像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 ++程序是链接在一起的独立单元。当语句来自不同的源文件时,它们应以什么顺序执行?它们在初始化静态对象方面的执行情况如何?静态初始化的当前状态足够复杂;我认为不允许在命名空间范围内使用表达式语句是一个不错的选择。