【问题标题】:static initialization confusion静态初始化混乱
【发布时间】:2010-06-14 10:26:59
【问题描述】:

我对 c++ 中的一些概念感到非常困惑。例如:我有以下两个文件

//file1.cpp
class test
{
    static int s;
    public:
    test(){s++;}
};

static test t;
int test::s=5;

//file2.cpp
#include<iostream>
using namespace std;
class test
{
    static int s;
    public:
    test(){s++;}
    static int get()
    {
    return s;
    }
};

static test t;

int main()
{
    cout<<test::get()<<endl;
}

现在我的问题是:
1. 两个文件的类定义不同,如何链接成功?
2.两个类的静态成员是否相关,因为我得到的输出是7。

请解释一下这个静态概念。

【问题讨论】:

    标签: c++ static initialization


    【解决方案1】:

    它们链接是因为链接器对 C++ 语言几乎一无所知。然而,如果你这样做,你就违反了单一定义规则,你的程序的行为将是未定义的。编写无效代码不是学习 C++ 的好方法。此外,您似乎对静态变量有很多疑问 - 这个概念实际上并不那么复杂 - 您使用的 C++ 教科书没有很好地解释它?

    【讨论】:

    • 好的..现在假设我在两个文件中编写了相同的类定义。那么我的第二个问题呢?两个类的静态成员是否相关?
    • @Happy 只有一个名为 test 的类,所以只能有一个静态变量。其他任何东西都是链接器产生的人工制品。
    • 我正在使用 Bruce Eckel 编写的 Thinking in c++。实际上,我正在阅读用于静态初始化订单惨败的 schwarz 计数器技术,但我无法理解为解释该技术而编写的代码。如果你有那本书,你能解释一下那个代码吗?
    • @Happy 如果您的问题是关于 Schwartz Counters 的,请专门询问它们。然而,尽管存在这样的事情,但编写依赖于不在同一编译单元中的对象的静态初始化顺序的代码是一个非常非常糟糕的主意。而且很容易不这样做。
    • 是的,我知道这是一件坏事,但我只是想了解一下概念。我将为 schwartz 计数器创建一个单独的线程,但在此之前我想澄清我对这个静态事物的怀疑。
    【解决方案2】:

    这些类(就链接器而言)是相同的。 get() 只是链接器永远不会看到的内联函数。

    类中的静态成员不会将成员限制在文件范围内,而是使其对所有类实例都是全局的。

    [edit:] 如果您也将int test::s=5; 放入第二个文件,则会出现链接器错误。

    【讨论】:

      【解决方案3】:

      静态是 C++ 中的一种奇怪的野兽,根据上下文有不同的含义。

      在File1.cpp中,类定义中的“static int s”表示s是一个静态成员,即对所有test实例都是通用的。

      但是,“静态测试t”有不同的含义:静态全局变量只存在于编译单元中,其他单元不可见。如果您对两个不同的事物使用相同的名称,这是为了避免链接器混淆。在现代 C++ 中,人们会为此使用匿名命名空间:

      namespace
      {
        test t;
      }
      

      这意味着 File1.cpp 中的 t 和 File2.cpp 中的 t 是独立的对象。

      在File2.cpp中,你还定义了一个静态方法get:静态方法是属于类而不是实例的方法,并且只能访问类的静态成员。

      您在示例中没有使用的最后一个静态变量是局部静态变量。局部静态变量在第一次执行时被初始化,并在整个执行过程中保持其值,有点像具有局部作用域的全局变量:

      int add()
      {
        static value = 0;
        value++;
        return value;
      }
      

      重复调用 add() 将返回 1,然后是 2,然后是 3...例如,这种本地静态构造对于本地缓存很有用。

      【讨论】:

        【解决方案4】:
        1. 即使两个文件具有不同的类定义,如何成功链接?

        您违反了一个定义规则 (ODR),因为您在不同的翻译单元中以不同的方式定义了同一个类。这会引发可怕的未定义行为。这意味着代码允许执行任何操作,包括但不限于执行您想要的操作、格式化硬盘或导致不明原因的日食。
        请注意,不要求编译器/链接器检测 ODR 违规。

        1. 两个类的静态成员 s 是否相关,因为我得到的输出为 7。

        也许他们是,也许他们不是。确实,未定义的行为可能会做任何事情。您所看到的只是您的编译器和链接器实现方式的产物。
        一个可能的实现会愉快地将所有对test::s 的引用链接到同一个实例,但让每个翻译单元拥有并链接到它们自己的t 对象。 (由于该类只有 inline 函数,因此链接器很可能甚至看不到 test 的任何内容,除了 test::s 和那些 t 实例。)

        【讨论】:

          【解决方案5】:

          C++ 实际工作方式的令人讨厌的副作用。使用命名空间使类名与外部不同。

          过去我在实践中使用了相当繁琐的规则:每个库和文件都必须有一个 private 命名空间,以避免此类链接冲突。或者将实用程序类直接放入主类定义中。无论如何:不要污染全局命名空间,最重要的是确保整个项目中名称的唯一性。 (写到现在我注意到我几乎一字不差地使用了 Java 中的概念。)

          我在 C++ 中寻找相当于 C 的静态的结果没有结果...

          【讨论】:

          • C++ 的静态概念与 C 完全相同,而且还有一些。
          • @neil:我怎样才能创建一个 static 方法或类,它在编译单元之外是不可见的? C 具有函数和数据 - 将 static 应用于它们使它们成为本地的。 C++ 有类 - 但我还没有找到任何东西可以让类成为编译单元的本地类。
          • 非类静态函数在 C++ 中的工作方式完全与在 C 中的工作方式相同。事实上,您不能拥有编译单元本地类静态函数(尽管你当然可以有私人的)并不与我的评论相矛盾。
          • @Neil。它确实与您的“加上一些”评论相矛盾。 C++ 将“静态”关键字用于其他目的这一事实并没有改变这样一个事实,即 C++ 并未提供 C 的“静态”提供的所有功能。在 C 中,可以使用“静态”获得编译单元本地语言支持的任何内容 - 在 C++ 中不能,类方法始终具有链接方式的全局范围。最初的问题不是来自 C++ 如何滥用关键字,而是如何将类实现链接器的可见性限制为单个编译单元。
          • C++ 具有所有 C 静态特性 plus 类静态和命名空间本地。我会说这是“加上一些”。这个对话框现在从我这边关闭了。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-11-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-06-28
          • 2020-04-14
          相关资源
          最近更新 更多