【问题标题】:Why do some const variables referring to some exported const variables get the value 0?为什么引用一些导出的 const 变量的一些 const 变量的值是 0?
【发布时间】:2009-05-28 12:35:53
【问题描述】:

考虑以下问题。我有两个导出的常量如下:

// somefile.h
extern const double cMyConstDouble;
extern const double cMyConstDouble2;

// somefile.cpp
const double cMyConstDouble = 3.14;
const double cMyConstDouble2 = 2.5*cMyConstDouble;

这些常量现在在其他地方被引用以定义两个静态(本地可见)常量:

// someotherfile.cpp
#include "somefile.h"
static const double cAnotherDouble = 1.1*cMyConstDouble;
static const double cAnotherDouble2 = 1.1*cMyConstDouble2;
printf("cAnotherDouble = %g, cAnotherDouble2 = %g\n",
       cAnotherDouble, cAnotherDouble2);

产生以下输出:

cAnotherDouble = 3.454, cAnotherDouble2 = 0

为什么第二个双0?我正在使用 .NET 2003 C++ 编译器 (13.10.3077)。

【问题讨论】:

    标签: c++ initialization constants double extern


    【解决方案1】:

    我不打算在这里深入探讨 extern 的问题,但是您为什么不将 const 放在适当的头文件中而忘记使用 extern“导出”它们呢?这就是 const 在 C++ 中的使用方式,以及它们具有内部链接的原因。

    换句话说:

    // someheader.h
    const double cMyConstDouble = 3.14;
    const double cMyConstDouble2 = 2.5*cMyConstDouble;
    

    并#include 该文件,无论您需要它们。

    【讨论】:

    • 那么,你涉足吗? :-) 无论如何+1。
    • 不是——我的解决方案不使用 extern。
    • 其实是有原因的。我们从您的方式转向了这种方式,因为它们造成了巨大的链接时间。
    • 那么你的代码库的架构有问题。我在过去十年中编写和审查的所有 C++ 代码都使用我的方式,完全没有问题。
    • 我一定会考虑您的意见。感谢您的回复。
    【解决方案2】:

    因为 cMyConstDouble 被声明为 extern,编译器无法采用它的值并且不会为 cMyConstDouble2 生成编译时初始化。由于 cMyConstDouble2 未在编译时初始化,因此其相对于 cAnotherDouble2 的初始化顺序是随机的(未定义)。请参阅static initialization fiasco 了解更多信息。

    【讨论】:

    • @Suma: 如果cMyConstDouble2 由于cMyConstDoubleextern 而不是恒定折叠的,那么cAnotherDouble 不应该发生同样的情况吗?这也取决于cMyConstDouble 对吗?为什么它的值被正确打印为 3.454
    • 模块之间的初始化顺序未定义。在一种情况下它已经完成,在第二种情况下没有。
    【解决方案3】:

    这样做很危险,因为您在一个源文件中的一个静态变量依赖于另一个 cpp 文件中的另一个静态变量。更多信息请查看static initialization fiasco

    【讨论】:

    • 这通常是危险的,但是当编译时通过常量初始化时这并不适用。我的回答指出了这样一个事实,虽然代码可能看起来像编译时初始化,但实际上并不是因为 extern。
    【解决方案4】:

    如果你在这里把cMyConstDouble2的初始化改成这个:

    const double cMyConstDouble2 = 2.5*3.14;
    

    那么你的程序应该是正确的。原因是变量

    • 有 POD 类型
    • 用常量表达式初始化(1)

    在静态初始化时进行初始化。这些初始化包括

    • 所有具有静态存储持续时间的对象的零初始化
    • 使用常量表达式初始化 POD 的初始化

    在您显示的变量中,只有cMyConstDouble 满足在静态初始化时完全初始化的两个条件。但是,cMyConstDouble2 没有,因为它的初始化器不满足常量表达式的要求。特别是,它包括一个不具有整数类型的变量(这里,它具有浮点类型)。但是,算术常量表达式中允许使用浮点 literals 。这就是为什么2.5*3.14 是一个算术常量表达式。这就是为什么将初始化程序更改为需要静态初始化的原因。


    如果您继续使用非常数表达式,cMyConstDouble2 会发生什么?答案是,你不知道。该标准允许对该变量进行静态初始化,但不要求它这样做。在您的情况下,它是动态初始化的 - 因此它在静态初始化时间之后的值仍然为零。为了感受一下复杂性,这里有一个例子:

    inline double fd() { return 1.0; }
    extern double d1;
    double d2 = d1; // unspecified:
                    // may be statically initialized to 0.0 or
                    // dynamically initialized to 1.0
    double d1 = fd(); // may be initialized statically to 1.0
    

    如果动态初始化不改变任何其他静态存储变量(在你的代码中满足)并且当静态初始化产生的值与当所有对象都没有改变时动态初始化产生的值相同时需要静态初始化将被动态初始化(在您的代码中也满足) - 然后允许静态初始化变量。上面代码中d2d1这两个变量同样满足这两个条件:

    d2分析

    • = d1 不会更改任何其他静态存储变量
    • d2d1都被动态初始化时,那么d2会被初始化为0.0,因为d2是在d1之前定义的,而d2的动态初始化会获取值d1 在静态初始化之后的状态(仅发生 d1 的零初始化)。

    d1分析

    • = fd() 不会更改任何其他静态存储变量
    • d2d1都被动态初始化时,= fd()会将d1初始化为1.0

    因此,编译器可能会将d1 静态初始化为1.0,因为同时满足可选静态初始化的两个条件。

    • 如果编译器决定动态初始化d1d2,那么d2将被初始化为0.0,因为它将获取 d1 的值,因为它刚刚在零初始化之后。

    • 然而如果编译器决定静态初始化d1,动态初始化d2,那么d2将被初始化为@ 987654358@,因为d2 的动态初始化将获取d1 的完全初始化值,因为它刚刚在静态初始化之后。

    我不确定d2 的值是什么,当d1 d2 被静态初始化,不过。也就是说,d2 应该抓取0.0 还是1.0,因为没有为静态初始化定义顺序。


    (1) 在考虑具有静态存储持续时间的对象的初始化顺序时,常量表达式也包括算术常量表达式(不仅是整数常量表达式)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-06
      • 2020-03-19
      • 1970-01-01
      • 2020-07-11
      • 2015-08-04
      相关资源
      最近更新 更多