【问题标题】:c++ is it valid to access members of static/global object before its constructor has been called?c++ 在调用其构造函数之前访问静态/全局对象的成员是否有效?
【发布时间】:2020-09-06 13:32:24
【问题描述】:

在执行该对象的构造函数之前(例如从全局新替换)调用全局命名空间中的对象的成员函数是否有效且定义明确(不询问是否漂亮) - 以及当此成员函数执行时,该对象的成员变量是否保证初始化为零并且访问(甚至写入)它们是否没有错误? 例如:

#include <iostream>
#include <memory>

using namespace std;

struct A {
  A() {
    new int();
  }
};

A a_;

struct B {
  B() : var(10) {
  }
  void print() {
    printf("var is %d\n", var);
    var = 5;
  }
  int var;
};

B b_;

void* operator new(size_t bytes) {
    b_.print();
    b_.print();
    return malloc(bytes);
}

int main()
{
    b_.print();
    return 0;
}

编译并打印

var is 0
var is 5
var is 10

但它是定义的行为吗?

【问题讨论】:

    标签: c++ language-lawyer new-operator


    【解决方案1】:

    确实,零初始化发生在前面,但在构造类实例之前调用非静态类方法是未定义的行为。方法调用本身变成了未定义的行为。

    在同一翻译单元中定义的全局(静态)范围内的对象按其声明顺序构造(并按相反顺序销毁)。

    这里首先构造a_,然后调用new重载,这会调用尚未构造的类实例的方法,这是未定义的行为。

    如果这两个对象在不同的​​翻译单元中定义,您最终会得到static initialization order fiasco

    即使您首先定义 b_,这仍然是未定义的行为,除非您可以保证任何其他翻译单元中的任何内容都不会最终调用此 new 重载。这包括 C++ 库。许多 C++ 实现的库确实分配了一些存储空间供自己使用,因此这很可能最终也是未定义的行为,即使定义顺序被颠倒了,在这里。

    【讨论】:

    • 这是否意味着不可能使用任何全局簿记结构来替换新的,因为它们很可能在执行自己的构造函数之前被访问?
    • 默认初始化和零初始化发生得足够早,因此如果您可以确保您的簿记由平凡的零或默认初始化的 POD 组成,而不是具有类方法的对象,那么这应该是迂腐的够了。
    • @user1282931 这样的结构可以在new 替换内做成一个静态变量。但它们并不完全是全球性的。在这种情况下,您可以创建一个函数Singleton getInstance(),它返回其静态变量。这将起作用并且保证返回初始化对象并且它是全局的。
    • 引用是[class.dtor]:对于具有非平凡构造函数的对象,在构造函数开始执行之前引用对象的任何非静态成员或基类会导致未定义的行为。
    • @user1282931 当然,int&amp; f(){static thread_local int n=10; return n;} 是有效代码。此外thread_local 暗示static。但要小心将它放在new 中,因为这样它的初始化就不能再次调用new
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-28
    • 1970-01-01
    • 1970-01-01
    • 2011-05-28
    • 1970-01-01
    相关资源
    最近更新 更多