问题 在C与C ++中初始化具有静态存储持续时间的对象[重复]


可能重复:
主要回报是什么? 

例如,以下代码编译时没有任何警告:

#include <stdio.h>

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。


6731
2018-03-29 14:41


起源

这肯定是未定义的行为...... - BlackBear
我想如果它做任何事情,那么它为i留出了内存,然后在赋值中它使用存储在该部分内存中的任何值(因此存储在i中的结果值将是不可预测的)。 - Hammerite
你的代码 不 编译。 gcc给出了错误 initializer element is not constant。 - 一二三
在 C++我认为,这是更好的形式使用 #include <cstdio> ......甚至可能 cout 和朋友而不是 fprintf - pmg
呵呵:用两者标记的问题 C 和 C++ 可能首先应该没有这些标签:D - pmg


答案:


(注意:我指的是当前的C ++标准)

我不是很确定这一点,但是,如果我对标准的解释是正确的,那么代码应该没问题,而不是UB。

该变量的第一次初始化是 零初始化 发生静态存储持续时间的对象的数量 在任何其他之前 初始化发生 (§3.6.2¶1)。

所以,首先 i 设置为零。

然后, 动态初始化 (即非零和非常数初始化)发生,因此它使用当前值 i (0)再次实际初始化它。最后它应该评估为1。

这似乎由§8.5¶6证实,它明确地说:

在任何其他初始化发生之前,任何静态存储持续时间对象占用的内存应在程序启动时进行零初始化。 [注意:在某些情况下,稍后会进行额外的初始化。 ]

(如果你在分析中发现一些缺陷,请在评论中告诉我,我会很高兴纠正/删除答案,这是很滑的地板,我很清楚:))


6
2018-03-29 14:52



部分 [basic.scope.pdecl] 也是关键...我同意它的定义很明确。 - Ben Voigt


在C ++中,它在语法上是正确的。在C中,您只能使用常量初始化全局变量。所以你的代码不能用C编译。

在C中,这是合法的BTW

int main()
{
   int i = i+1;
}

3.3.1 / 1声明点

声明的名称是 在完整的声明者之后立即 在它的初始化程序之前(如果有的话)。

行为很好地定义为 §3.6.2/1 其中说:

“在进行任何其他初始化之前,具有静态存储持续时间(3.7.1)的对象应进行零初始化(8.5)。”


5
2018-03-29 14:46



@Prasoon Saurav ::你确定提问者的代码是未定义的吗?“ 具有静态存储持续时间(3.7.1)的对象应在任何其他初始化发生之前进行零初始化(8.5)。“ - Sadique
@Acme:是的!!!! - Prasoon Saurav
@Prasoon Saurav ::嗯? - Sadique
@Acme:对不起。我以为你的意思很明确。它定义明确。 - Prasoon Saurav
@Acme:我在你添加评论前3分钟编辑了我的答案。 - Prasoon Saurav


您的代码不合法​​C.

如果您的编译器在没有诊断的情况下编译它,
你的编译器不是C编译器

您必须使用常量来初始化变量。

在您的代码中,初始化表达式( i + 1 )不是常数。

这违反了6.7.8 / 4:

初始化程序[...]中的所有表达式都应是常量表达式或字符串文字。


1
2018-03-29 14:49





该代码在C中是非法的。

initializer element is not constant

C99 - 6.7.8初始化

具有静态存储持续时间的对象的初始值设定项中的所有表达式都应为 常量表达式或字符串文字。

它在C ++中有效。

C ++标准状态 3.6.2非本地对象的初始化

具有静态存储持续时间(3.7.1)的对象应在任何其他初始化发生之前进行零初始化(8.5)。


1
2018-03-29 14:42



参考这个? - Benoit
这是我对全局范围初始化的期望。函数内部是否相同? - André Caron
@AndréCaron::然后,由于带有存储类的变量,它将是未定义的行为 auto 默认情况下不会初始化为任何值。 - Sadique
您的帖子没有回答为什么代码段不会在C ++中出现任何错误。 - Prasoon Saurav


您无法使用任何函数外的其他变量为变量赋值。该声明 i + 1; 在运行时期间进行评估 int i = i + 1; 在任何函数之外,因此需要在编译时进行评估。


0
2018-03-29 14:45





它是否真的在语法上是非法的我不确定(它肯定在方法中有效)。但是,正如您所建议的那样,这是一个语义问题,编译器应该发出警告 i 没有初始化使用。 IMO C / C ++编译器通常不会警告这样的事情(例如Java会给出错误),尽管你可以通过添加这样的警告 -Wall 参数到gcc。


0
2018-03-29 14:47



没有警告,因为没有什么值得警告的。如果您将代码编译为C ++,则可以很好地定义它。如果您将代码编译为C,则编译器会发出错误,因为该构造在C中是非法的。 - IInspectable


由于编译器接受语句并发出低级代码供CPU使用,因此必须将实际发生的事情分开。它会是这样的:

  1. 为“i”创建一个内存插槽。
  2. 将内存初始化为零(正常默认行为)。
  3. 读取“i”的值(为零)。
  4. 加1。
  5. 将其存储在“i”中。

0
2018-03-29 14:47





我不会重复相同的事情:它是未定义的行为,你不应该这样做......但提供一个用例(这是一个常见的习语),它说明了为什么有时允许在那里使用变量很有意思(在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 <typename T>
struct NullObjectPattern { // intentionally out of order:
   T* ptr;
   T null;
   NullObjectPattern() : ptr( &null ), null() {}

   T& operator*() { return *ptr; }
   T* operator->() { return ptr; }
};

null 尚未初始化,在表达式中使用它只接受它的地址(但不取消引用它)是明确定义的:存储位置存在,它已被分配并存在。对象本身尚未初始化,因此解除引用它将导致UB,但是在表达式中使用未初始化对象的事实并不意味着代码实际上是错误的。


0
2018-03-29 15:03



行为在C ++中定义良好。 - Prasoon Saurav