C与C ++中具有静态存储持续时间的对象的初始化

时间:2011-03-29 14:41:14

标签: c++ c initialization definition

  

可能重复:
  What does main return?

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

#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。

9 个答案:

答案 0 :(得分:7)

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

我对此并不十分确定,但是,如果我对标准的解释是正确的,那么代码应该没问题,而不是UB。

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

因此,首先将i设置为零。

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

§8.5¶6似乎证实了这一点,明确地说:

  

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

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

答案 1 :(得分:5)

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

在C中,这是合法的BTW

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

3.3.1 / 1声明要点

  

在完整的声明符之后及其初始化程序(如果有的话)之前,名称的声明点是

根据§3.6.2/1明确定义了行为:

  

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

答案 2 :(得分:1)

该代码在C中是非法的。

initializer element is not constant

C99 - 6.7.8初始化

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

在C ++中有效。

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

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

答案 3 :(得分:1)

您的代码不合法​​C.

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

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

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

这违反了6.7.8 / 4:

  

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

答案 4 :(得分:0)

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

答案 5 :(得分:0)

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

答案 6 :(得分:0)

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

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

答案 7 :(得分:0)

我不会重复相同的事情:它是未定义的行为,你不应该这样做......但是提供一个用例(这是一个常见的习语),它说明为什么有时允许使用变量那里(在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,但是在表达式中使用未初始化的对象并不意味着代码实际上是错误的。

答案 8 :(得分:0)

要解决有关“i在声明之前使用过的问题,对吗?”

不在C ++中。 [basic.scope.pdecl]

  

名称的声明紧接在其完整声明者(第8条)之后和初始化程序之前(如果有),除非如下所述。 [示例:

int  x  =  12;
{  int  x  =  x;  }
     

这里第二个x用自己的(不确定的)值初始化。 - 结束示例]