一般来说,函数作用域的静态变量在控制第一次到达变量声明时初始化(例如,当函数第一次被调用时),但在某些情况下,它们可以提前初始化。在什么情况下,我们可以保证静态函数作用域变量的构造函数在函数第一次被调用之前被热调用?
具体来说,我有一个这样的例子:
(On godbolt)
// a.h
void initA();
void setA(int val);
int getA();
// a.cpp
#include "a.h"
static int a;
void initA() {
a = 1;
}
void setA(int val) {
a = val;
}
int getA() {
return a;
}
// b.h
class B {
public:
B();
};
// b.cpp
#include "b.h"
#include "a.h"
B::B() {
setA(12);
}
// main.cpp
#include "a.h"
#include "b.h"
#include <iostream>
void local_statics();
int main() {
initA(); // sets a static namespace-scope int `a` to 1
local_statics();
std::cout << getA() << std::endl; // Is this guaranteed to print 12?
}
void local_statics() {
// Constructor of B sets a static namespace-scope int `a` to 12
static B b;
}
标准是否保证a
将被设置为12?b
没有被零初始化或常量初始化(因为构造函数不是constexpr),所以根据一般的标准,它将在控制第一次通过其声明时初始化(即,当函数被调用时),在这种情况下,a
将如预期的那样具有值12:
零初始化(8.5)所有具有静态存储持续时间的块作用域变量(3.7.1)或线程存储持续时间(3.7.2)在任何其他初始化发生之前执行。常量初始化(3.6.2)具有静态存储持续时间的块作用域实体,如果适用,在首次进入其区块之前执行。一个实现被允许执行其他区块的早期初始化-具有静态或线程存储持续时间的范围变量,其条件与允许实现在命名空间范围中静态初始化具有静态或线程存储持续时间的变量的条件相同(3.6.2).否则,此类变量在控制第一次通过其声明时被初始化;此类变量在其初始化完成时被视为已初始化
然而,我不太清楚在什么情况下“允许实现执行其他块作用域变量的早期初始化.在相同的条件下,允许实现静态初始化命名空间作用域中具有静态或线程存储持续时间的变量(3.6.2)”。在3.6.2节中列出的条件是:
允许实现将具有静态存储持续时间的非局部变量的初始化作为静态初始化来执行,即使这样的初始化不需要静态地完成,只要
- 初始化的动态版本在初始化之前不改变命名空间范围的任何其他对象的值,并且
- 如果所有不需要静态初始化的变量都被动态初始化,则初始化的静态版本在初始化的变量中产生与动态初始化将产生的值相同的值。
我只是不太明白这两个条件,以及它们究竟如何适用。
1条答案
按热度按时间d5vmydt91#
您引用的第一个措辞块已被C17替换,并作为通过CWG 2026针对先前C修订版的缺陷报告。
措辞现在更清楚了,[stmt.dcl]/3现在只说:
具有静态存储持续时间或线程存储持续时间的块变量的动态初始化在控制第一次通过其声明时执行; [...]
basic.start(https://eel.is/c++draft/basic.start)已被修改为以与非本地静态存储持续时间变量相同的方式处理本地静态存储持续时间变量的静态初始化。
这意味着本地和非本地静态存储持续时间变量的初始化行为现在是相同的,除了如果发生局部变量的动态初始化,那么它发生在控制首先通过其定义时(具有关于异常和线程安全的特殊规则)。
第二个引用的块仍然在标准中,只是“non-local”限定符被删除了。
编译器可以在这里执行
b
的静态初始化:a
只在静态初始化后才被修改。B
中产生的值总是相同的,因为类型B
没有状态。当编译器执行
b
的静态初始化时,程序将打印1
。这忽略了初始化选择导致的不同副作用,这是标准中已知的未解决缺陷,在开放的CWG issue 1294中进行了跟踪。