为什么可以修改在函数内部声明的静态常量变量?

Why is it possible to modify a static const variable that is declared inside a function?

我正在为我的游戏引擎实现一个 ECS 框架,并研究在运行时识别组件类型的方法。这意味着我可以在内存中动态地将相似类型的组件连续分组。例如,我可以为我的系统可以循环的 PositionVelocity 组件设置两个单独的数组。

我目前使用 typeid(),但我看到一篇文章使用 static const 变量为每种类型生成唯一 ID。总体思路可以总结在下面的代码片段中:

#include <iostream>

struct Position { float x, y; };
struct Velocity { float dx, dy; };

template <typename T>
int test(int x) {
    static const int y = x;
    return y;
}

int main(int argc, char **argv) {
    std::cout << test<Position>(1) << " ";
    std::cout << test<Position>(2) << " ";
    std::cout << test<Velocity>(3) << " ";
    return 0;
}

这会输出 1 1 3,但我预计由于它试图修改常量(特别是在第二次调用时),所以它会编译失败。为什么这样做有效?

静态变量只初始化一次。

第二次(及后续)初始化被跳过。

这里还有两个不同的函数test。每个唯一模板参数类型 1 个。

一个示例,用于对您的代码进行简单的函数调用:

auto f() { return test<Position>(1); }

这将产生以下汇编代码,您可以在其中看到检查以验证变量是否已初始化或 return 是否已设置值。如果没有设置,那么会以线程安全的方式设置。

f():
        movzx   eax, BYTE PTR guard variable for int test<Position>(int)::y[rip]
        test    al, al   // <----- check if intialized
        je      .L13     // <----- if not initialized go to .L13
                         // otherwise return the value
        mov     eax, DWORD PTR int test<Position>(int)::y[rip]
        ret
.L13:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:guard variable for int test<Position>(int)::y
        call    __cxa_guard_acquire
        test    eax, eax
        jne     .L14
        mov     eax, DWORD PTR int test<Position>(int)::y[rip]
        add     rsp, 8
        ret
.L14:
        mov     DWORD PTR int test<Position>(int)::y[rip], 1
        mov     edi, OFFSET FLAT:guard variable for int test<Position>(int)::y
        call    __cxa_guard_release
        mov     eax, DWORD PTR int test<Position>(int)::y[rip]
        add     rsp, 8
        ret

这里,static const int y = x; yconst表示是non-mutablestatic是程序中的存储时长。

如果您尝试在后续语句中直接修改 y,编译器将抛出错误,如果尝试间接修改,则为未定义行为。

来自 c++ 参考,

const object - an object whose type is const-qualified, or a non-mutable subobject of a const object. Such object cannot be modified: attempt to do so directly is a compile-time error, and attempt to do so indirectly (e.g., by modifying the const object through a reference or pointer to non-const type) results in undefined behavior.