为什么 gcc 接受将方法中的静态变量初始化为对象的字段?

Why gcc accepts initializing the static variable within the method to the field of the object?

提问人:Audrius Meškauskas 提问时间:9/19/2023 最后编辑:Audrius Meškauskas 更新时间:9/23/2023 访问量:92

问:

class MyClass {
public:
    int obj_field;

    MyClass(int val) : obj_field(val) {}

    void myMethod() {
        static int my_static_var = obj_field;  // <--- Why is this okay?
        // ... 
    }
};

我深信这是错误的,不应该编译,因为我确信它们只能初始化为 constexpr(假设值在执行开始之前初始化)。令我惊讶的是,它在 gcc 9.4.0、C++17 中没有编译任何错误。它是否能够将静态变量的初始化推迟到第一次调用或未定义的行为更有可能发生?

我知道如果它能起作用,静态值可能会从调用该方法的第一个对象中选取。

C++ 对象 gcc static-order-fiasco

评论

2赞 Ted Lyngmo 9/19/2023
“它是否能够将静态变量的初始化推迟到第一次调用” - 是的。
2赞 JaMiT 9/19/2023
“我深信这是错误的,不应该编译。--为什么?函数中的静态变量(成员函数和非成员函数)是完全标准的。
3赞 Richard Critten 9/19/2023
静态局部变量 - “...在控件第一次通过其声明时初始化...“请参阅 en.cppreference.com/w/cpp/language/...
0赞 Audrius Meškauskas 9/19/2023
@JaMiT因为我确定它们只能初始化为 constexpr
1赞 Ted Lyngmo 9/19/2023
如果你这样做,它可能会变得更加明显:由于一个变量只能初始化一次,所以它必须在第一次调用函数时初始化。void myMethod(int x) { static int my_static_var = x; }

答:

5赞 eerorika 9/19/2023 #1

它是否能够将静态变量的初始化推迟到第一次调用

这实际上是初始化局部静态变量的点。更具体地说,初始化发生在执行首次到达声明点时。

还是更有可能出现未定义的行为?

示例中没有 UB。

为什么 gcc 接受将方法中的静态变量初始化为对象的字段?

因为这样做是很好的形式。

评论

3赞 NathanOliver 9/19/2023
可能还想补充一点,即使在多线程中,静态变量也保证只初始化一次。
0赞 eerorika 9/19/2023
@NathanOliver这足以说明,我发现它足以让你发表评论。旁注:此保证是在 C++ 11 中添加的。
0赞 JaMiT 9/23/2023 #2

(假设值在执行开始之前初始化)

这个假设(可能)有两点是错误的。

首先,在函数中初始化变量的时间与在命名空间范围内初始化变量的时间不同。虽然全局静力学(大部分)在执行之前被初始化(我猜这就是你所想到的),但局部静力学在执行其定义之前不会被初始化。这意味着直到(至少)第一次调用函数时才会初始化它。staticstaticmain

其次,执行不是从函数开始的。全局变量的初始化分为两个阶段。第一阶段处理您知道的常量表达式初始化。第二阶段是那些需要执行代码进行构造的变量。第二阶段可以在功能开始之前完成。(有一些回旋余地,允许第二阶段在开始 .)staticmainmain

事实上,在初始化全局变量时执行代码的能力是的关键因素。更好的是,解决一些静态顺序惨败的一种方法是通过将变量定义包装在 getter 函数中来控制初始化顺序。这可确保在第一次调用函数时初始化变量,这必然是在第一次使用变量值之前。所以你可以说,在你的问题中,你已经解决了一个你不知道存在的问题。:)staticstatic

对于那些喜欢实验的人来说,这里有一个简单的例子,说明命名空间范围内的变量可以使用非常量表达式进行初始化。static

#include <chrono>
#include <iostream>

static const auto start_time =
    std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());

int main() {
    std::cout << "Program started at: " << std::ctime(&start_time) << "\n";
}

直接回答这个问题:
是的,初始化被推迟到第一次调用 ,并且值将来自该调用的对象。
my_static_varmyMethod()*this

参见 函数级静态变量何时分配/初始化?