【问题标题】:Initialization of Objects with Static Storage Duration in C vs C++ [duplicate]在 C 与 C++ 中使用静态存储持续时间初始化对象 [重复]
【发布时间】:2011-03-29 14:41:14
【问题描述】:

可能重复:
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。

【问题讨论】:

  • 这肯定是未定义的行为......
  • 我想如果它做任何事情,那么它会为 i 留出内存,然后在赋值中它使用存储在该部分内存中的任何值(因此存储在 i 中的结果值将不可预测)。
  • 您的代码没有编译。 gcc 给出错误initializer element is not constant。
  • 在C++ 中,我认为使用#include &lt;cstdio&gt; ... 甚至可能是cout 和朋友而不是fprintf 是更好的形式
  • 呵呵:用C 和C++ 标记的问题可能一开始就不应该有这些标记:D

标签: c++ c initialization definition


【解决方案1】:

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

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

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

所以,首先将i 设置为零。

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

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

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

(如果您在分析中发现一些缺陷,请在 cmets 中告诉我,我很乐意更正/删除答案,地板很滑,我很清楚 :))

【讨论】:

  • [basic.scope.pdecl] 部分也是关键...我同意它定义明确。
【解决方案2】:

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

在 C 中这是合法的 BTW

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

3.3.1/1 声明点

名称的声明点是紧接在其完整声明符之后和其初始化程序(如果有)之前。

根据§3.6.2/1 对行为进行了很好的定义:

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

【讨论】:

  • @Prasoon Saurav:: 你确定提问者的代码是未定义的吗?” 具有静态存储持续时间(3.7.1)的对象应在任何其他初始化之前进行零初始化(8.5)发生。"
  • @Acme:对不起。我以为你的意思是定义明确。它定义明确。
  • @Acme :我在您添加评论前 3 分钟编辑了我的答案。
  • 有人能解释为什么这个答案被否决了吗?
  • 在您的 sn-p 中,行为是未定义的,因为 i 变量具有自动存储,而不是静态存储,因此它几乎可以满足 C 编译器的任何需求。如果定义是在 main 之外进行的,那么它的定义就很好了。
【解决方案3】:

你的代码是不合法的 C.

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

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

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

这违反了 6.7.8/4:

初始化器中的所有表达式 [...] 应为常量表达式或字符串字面量。

【讨论】:

    【解决方案4】:

    代码在 C 中是非法的。

    initializer element is not constant

    C99 -- 6.7.8 初始化

    具有静态存储持续时间的对象的初始化程序中的所有表达式应为 常量表达式或字符串文字。

    在 C++ 中有效。

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

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

    【讨论】:

    • 这是我对全局范围初始化的期望。函数内部也一样吗?
    • @André Caron :: 那么这将是未定义的行为,因为存储类为 auto 的变量默认情况下不会初始化为任何值。
    • 你的帖子没有回答为什么代码 sn-p 在 C++ 中没有给出任何错误。
    【解决方案5】:

    您不能使用任何函数之外的另一个变量为变量赋值。语句i + 1; 在运行时评估,而int i = i + 1; 在任何函数之外,因此需要在编译时评估。

    【讨论】:

      【解决方案6】:

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

      【讨论】:

      • 没有警告,因为没有什么可警告的。如果您将代码编译为 C++,那么它的定义是明确的。如果您将代码编译为 C,编译器会发出错误,因为该构造在 C 中是非法的。
      【解决方案7】:

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

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

      【讨论】:

        【解决方案8】:

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

        【讨论】:

        • 行为在 C++ 中定义良好。
        【解决方案9】:

        解决您关于“i 在声明之前使用它的问题,对吗?”

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

        名称的声明点紧接在其完整声明符(第 8 条)之后和其初始化器(如果有)之前,除非以下说明。 [ 例子:

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

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

        【讨论】:

          猜你喜欢
          • 2016-11-06
          • 1970-01-01
          • 1970-01-01
          • 2017-05-26
          • 2015-04-28
          • 2020-02-17
          • 1970-01-01
          • 2011-09-09
          • 1970-01-01
          相关资源
          最近更新 更多