在C与C ++中初始化具有静态存储持续时间的对象
可能重复:
主要回报是什么?
例如,以下代码编译时没有任何警告:
#include int i = i + 1; int main(int argc, char *argv[]) { fprintf (stderr, "%d\n", i); return 0; }
我认为这在语法上是非法的,因为i
在它被声明之前被使用,是不是?
在我看来, int i = i + 1;
肯定是一个错误,为什么编译器不警告它呢? 我使用gcc 4.5.1。
(注意:我指的是当前的C ++标准)
我不是很确定这一点,但是,如果我对标准的解释是正确的,那么代码应该没问题,而不是UB。
该变量的第一次初始化是具有静态存储持续时间的对象的零初始化 ,该静态存储持续时间在任何其他初始化发生之前发生 (§3.6.2¶1)。
所以,首先i
被设置为零。
然后,进行动态初始化 (即非零和非常量初始化),因此它使用i
(0)的当前值再次实际初始化它。 最后它应该评估为1。
这似乎由§8.5¶6证实,它明确地说:
在任何其他初始化发生之前,任何静态存储持续时间对象占用的内存应在程序启动时进行零初始化。 [注意:在某些情况下,稍后会进行额外的初始化。 ]
(如果你在分析中发现一些缺陷,请在评论中告诉我,我会很高兴纠正/删除答案,这是很滑的地板,我很清楚:))
在C ++中,它在语法上是正确的。 在C中,您只能使用常量初始化全局变量。 所以你的代码不能用C编译。
在C中,这是合法的BTW
int main() { int i = i+1; }
3.3.1 / 1声明点
名称的声明点紧跟在完整的声明符之后和初始化符号之前(如果有的话)。
根据§3.6.2/1
明确定义了这种行为:
“在进行任何其他初始化之前,具有静态存储持续时间(3.7.1)的对象应进行零初始化(8.5)。”
您的代码不合法C.
如果您的编译器在没有诊断的情况下编译它,
你的编译器不是C编译器
您必须使用常量来初始化变量。
在您的代码中,初始化表达式( i + 1
)不是常量。
这违反了6.7.8 / 4:
初始化程序[…]中的所有表达式都应是常量表达式或字符串文字。
该代码在C中是非法的。
initializer element is not constant
C99 – 6.7.8初始化
具有静态存储持续时间的对象的初始值设定项中的所有表达式应为常量表达式或字符串文字。
它在C ++中有效。
3.6.2中的 C ++标准状态非本地对象的初始化 :
具有静态存储持续时间(3.7.1)的对象应在任何其他初始化发生之前进行零初始化(8.5)。
您无法使用任何函数外的其他变量为变量赋值。 陈述i + 1;
在运行期间进行评估,而int i = i + 1;
在任何函数之外,因此需要在编译时进行评估。
它是否真的在语法上是非法的我不确定(它肯定在方法中有效)。 但是,正如您所建议的那样,这是一个语义问题,编译器应该发出警告,因为i
没有初始化使用。 IMO C / C ++编译器通常不会警告这样的事情(例如Java会给出错误),尽管你可以通过向gcc添加-Wall
参数来打开这样的警告。
由于编译器接受语句并发出低级代码供CPU使用,因此必须将实际发生的事情分开。 它会是这样的:
- 为“i”创建一个内存插槽。
- 将内存初始化为零(正常默认行为)。
- 读取“i”的值(为零)。
- 加1。
- 将其存储在“i”中。
我不会重复相同的事情:它是未定义的行为,你不应该这样做……但提供一个用例(这是一个常见的习语),它说明了为什么有时允许在那里使用变量很有意思(在C):
int * p = malloc( 10 * sizeof *p );
如果不允许在右侧使用p
,那将是编译器错误。 您可以通过明确声明rhs中的类型来规避它:
int * p = malloc( 10 * sizeof(int) );
但是,如果稍后更改类型,则容易出现细微错误,因为编译器不会检测到这种情况:
double * p = malloc( 10 * sizeof(int) ); // will compile and probably cause havoc later
现在,在C ++中,我只能假设它是为了向后兼容。 另请注意,某些编译器将能够检测到无效使用并从未更新的未初始化变量组中触发警告:
int i = i + 1; // ^ uninitialized read
但是,在C ++中还有其他情况,您可以将引用/指针传递给未初始化的对象,这非常好。 考虑:
template struct NullObjectPattern { // intentionally out of order: T* ptr; T null; NullObjectPattern() : ptr( &null ), null() {} T& operator*() { return *ptr; } T* operator->() { return ptr; } };
虽然null
尚未初始化,但在一个只占用它的地址(但不取消引用它)的表达式中使用它是明确定义的:内存位置存在,它已被分配并存在。 对象本身尚未初始化,因此解除引用它将导致UB,但是在表达式中使用未初始化对象的事实并不意味着代码实际上是错误的。
为了解决你的问题“ i
在申报之前就被使用了,对吧?”
不是在C ++中。 [basic.scope.pdecl]
说
声明的声明点在其完整的声明者(第8条)之后和初始化者 (如果有的话)之前,除非如下所述。 [ 例如:
int x = 12; { int x = x; }
这里第二个
x
用它自己的(不确定的)值初始化。 – 结束例子 ]