函数内static constexpr变量是否无线程同步开销且编译期初始化?
问题背景
本问题及代码灵感来自Jason Turner的C++ Weekly节目:Stop Using constexpr (And Use This Instead!)
假设存在如下代码:
constexpr auto getArr() { std::array<int,10> arr; for (int i = 0; i < 10; ++i) { arr[i] = i*2; } return arr; } auto getVal(int i) { static constexpr auto arr = getArr(); return arr[i] + 1; } int main() { return getVal(4); }
我理解,将函数局部变量声明为static constexpr时,可保证变量仅初始化一次(static特性),且因是constexpr,无需编译器为无法证明单线程访问时添加的线程安全开销。但C++标准是否对此做出了保证?是否有标准条款可确保static constexpr auto arr = getArr();这一行不会让编译器添加互斥锁或其他线程保护机制?
Jason Turner的节目及相关Stack Overflow问题均未提及局部static变量可能带来的线程安全开销,而这正是我需要明确答案的核心点——最好能指出标准相关条款。
明确来说:我能否确定getVal()函数中的arr是在编译期初始化,且无需任何线程同步?
解答
你完全可以确定arr会在编译期完成初始化,且不需要任何线程同步机制,以下是C++标准中的依据:
编译期初始化的保证
根据C++17及以后的标准,constexpr变量如果满足常量表达式要求,必须在编译期完成初始化。对于局部static constexpr变量,由于getArr()是constexpr函数,且返回的std::array初始化过程完全符合常量表达式规则,arr的初始化会被编译器在编译阶段完成,不会推迟到运行时。线程安全开销的免除
C标准中关于局部静态变量的线程安全(即“魔术静态”,C11引入)仅适用于需要在运行时初始化的静态变量。而static constexpr变量属于常量初始化(constant initialization),是静态初始化的一种,完全在编译/链接阶段完成,不参与动态初始化流程,因此不受动态初始化线程安全规则的约束,自然不会产生任何线程同步开销。
具体可参考C++标准的[basic.start.dynamic]条款:动态初始化的对象才会涉及线程安全的初始化保证,而常量初始化的对象不涉及运行时的初始化竞争,所以编译器没有理由为其添加互斥锁等线程保护机制。
内容的提问来源于stack exchange,提问作者cptFracassa

