【问题标题】:Referencing a possibly destroyed static object引用一个可能被破坏的静态对象
【发布时间】:2017-05-20 02:16:33
【问题描述】:

假设我有以下代码

Something.hpp

#pragma once

class Something {
public:
    static Something& get();
private: 
    Something();
};

Something.cpp

#include "Something.hpp"
#include <iostream>
using namespace std;

Something& Something::get() {
    static Something something;
    return something;
}
Something::Something() {
    cout << "Something()" << endl;
}

main.cpp

#include <iostream>
using namespace std;

struct SomethingElse {
    ~SomethingElse() {
        Something::get();
        cout << "~SomethingElse" << endl; 
    }
};

void func() {
    static SomethingElse something_else;
    // do something with something_else
}

int main() {
    func();
    return 0;
}

可以创建多个Something 对象的实例吗?标准是否对序列化静态对象的销毁有任何规定?

注意我知道跨不同翻译单元时文件级静态变量的破坏是未定义的,我想知道在函数作用域静态变量的情况下会发生什么 (which have the double-checked locking pattern built into the C++ runtime)具有文件级静态变量的相同翻译单元案例,编译器很容易根据变量在代码中的布局方式(静态)确保通过构造和销毁进行序列化,但是当变量是动态延迟创建时会发生什么函数被调用了吗?

注意原始变量呢?我们可以期望它们在程序结束之前包含它们的值吗?因为它们不需要被销毁。


编辑

在 cppreference.com (http://en.cppreference.com/w/cpp/utility/program/exit) 上找到这个

如果线程本地或静态对象 A 的构造函数或动态初始化的完成顺序在线程本地或静态对象 B 之前,则 B 的销毁完成顺序在 A 的销毁开始之前

如果这是真的,那么每个静态对象的销毁都会被序列化?但我也发现了这个

https://isocpp.org/wiki/faq/ctors#construct-on-first-use-v2 与标准相矛盾

【问题讨论】:

  • 两个字:共享指针
  • 我在标准中找不到关于这种特殊情况的任何信息 - 在终止期间初始化静态对象。我会保证它的安全并假设它是未定义的。
  • @molbdnilo 也许这个问题应该在别处发布?我只是不知道该去哪里

标签: c++ c++11 static-variables


【解决方案1】:

静态(和全局非静态)对象的构造和销毁顺序在 C++ 规范中明确定义,对于单个 translation unit

如果您有多个翻译单元(多个源文件),则未定义 TU 之间的构建/销毁顺序。

所以你展示的代码可以有未定义的行为

【讨论】:

  • 评论archived here。请注意,如果对话超过 15 cmets,最好使用聊天。
【解决方案2】:

[stmt.dcl] ¶4

在控制第一次通过其声明时执行具有静态存储持续时间或线程存储持续时间的块范围变量的动态初始化。

[basic.start.term] ¶1

如果具有静态存储持续时间的对象的构造函数或动态初始化的完成顺序在另一个之前,则第二个的析构函数的完成顺序在第一个的析构函数的启动之前。

¶2

如果函数包含已被销毁的静态或线程存储持续时间的块范围对象,并且在具有静态或线程存储持续时间的对象的销毁期间调用该函数,则程序具有未定义的行为 如果控制流通过了先前销毁的块作用域对象的定义。

SomethingElse 的析构函数中,我们冒着调用这种未定义行为的风险:

SomethingElse::~SomethingElse() {
    Something::get();
}

如果有SomethingElse的实例是静态存储时长,那么有四种可能:

  1. Something 的单个实例是在 SomethingElse 之前构造的。它的销毁会在SomethingElse之后发生,所以行为是很好定义的。

  2. Something 的单个实例是在SomethingElse 之后构造的。它的销毁将在SomethingElse 之前发生,因此如上所述未定义行为。

  3. Something 的单个实例是在不同的线程中构造的,没有与SomethingElse 的构造同步。破坏可能同时发生,因此行为未定义。

  4. 尚未构造Something 的实例(即这是对Something::get 的第一次调用)。在这种情况下,程序要求在SomethingElse之后构造Something,这意味着Something的销毁必须发生在SomethingElse之前,但是由于SomethingElse的销毁已经开始,这是一个矛盾,并且行为是不确定的。 (从技术上讲,“sequenced before”关系中有一个循环。)

【讨论】:

  • 这是否意味着跨翻译单元的构建和销毁是明确定义的?
  • @Curious 对于块范围内的静态,是的。对于命名空间范围内的静态(此处无关),它是未指定的,除了对象保证以初始化的相反顺序被销毁。在线程启动后首次使用 odr 的对象可以同时初始化。
  • 你确定吗? SO上到处都有很多答案似乎认为对于函数范围内的静态变量,不同翻译单元的破坏顺序是未指定的
  • @Curious This answer 同意我的观点(翻译单位在 cmets 中讨论)。我找不到不同意的。
  • isocpp.org/wiki/faq/ctors#construct-on-first-use-v2,它不同意。我不知道为什么会有这样的歧义
猜你喜欢
  • 2018-02-26
  • 1970-01-01
  • 1970-01-01
  • 2021-06-11
  • 2010-12-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-26
相关资源
最近更新 更多