C++ 函数中的静态constexpr变量有意义吗?

C++ 函数中的静态constexpr变量有意义吗?,c++,static,c++11,constexpr,C++,Static,C++11,Constexpr,如果我在一个函数(比如一个大数组)中有一个变量,那么同时声明它static和constexpr有意义吗constexpr保证数组是在编译时创建的,那么static会没有用处吗 void f() { static constexpr int x [] = { // a few thousand elements }; // do something with the array } 就生成的代码或语义而言,static是否真的在那里做任何事情?简单的回答是

如果我在一个函数(比如一个大数组)中有一个变量,那么同时声明它
static
constexpr
有意义吗
constexpr
保证数组是在编译时创建的,那么
static
会没有用处吗

void f() {
    static constexpr int x [] = {
        // a few thousand elements
    };
    // do something with the array
}

就生成的代码或语义而言,
static
是否真的在那里做任何事情?

简单的回答是,
static
不仅有用,而且总是需要的

首先,请注意
static
constexpr
是完全独立的<代码>静态定义对象在执行期间的生存期
constexpr
指定对象在编译期间应可用。编译和执行在时间和空间上是不相交和不连续的。因此,一旦编译了程序,
constexpr
就不再相关了

声明的每个变量
constexpr
都是隐式
const
,但是
const
static
几乎是正交的(除了与
static const
整数的交互作用)

C++
对象模型(§1.9)要求除位字段以外的所有对象至少占用一个字节的内存并具有地址;此外,在给定时刻可在程序中观察到的所有此类对象必须具有不同的地址(第6段)。这并不需要编译器在堆栈上为使用本地非静态常量数组的函数的每次调用创建一个新数组,因为编译器可以像躲在
原则中一样躲在
原则中,前提是它可以证明不能观察到其他这样的对象

不幸的是,要证明这一点并不容易,除非该函数是平凡的(例如,它不调用任何其他函数,其主体在转换单元中不可见),因为数组或多或少都是地址。因此,在大多数情况下,每次调用时都必须在堆栈上重新创建非静态的
const(expr)
数组,这就无法在编译时计算它

另一方面,本地
静态const
对象由所有观察者共享,并且即使从未调用其中定义的函数,也可能被初始化。因此,上述任何一项都不适用,编译器不仅可以自由地生成它的单个实例;它可以在只读存储器中自由地生成它的单个实例

因此,您应该在示例中明确使用
static constexpr


但是,有一种情况是您不想使用
static constexpr
。除非
constexpr
声明的对象是或声明的
static
,否则编译器完全可以不包含它。这非常有用,因为它允许使用编译时临时
constexpr
数组,而不会用不必要的字节污染编译后的程序。在这种情况下,您显然不想使用
static
,因为
static
可能会强制对象在运行时存在。

除了给定的答案之外,值得注意的是编译器不需要在编译时初始化
constepr
变量,知道
constexpr
static constexpr
之间的区别在于使用
static constexpr
可以确保变量只初始化一次

下面的代码演示了如何多次初始化
constexpr
变量(尽管值相同),而
static constexpr
肯定只初始化一次

此外,代码还比较了
constepr
const
结合使用
static
的优点

#include <iostream>
#include <string>
#include <cassert>
#include <sstream>

const short const_short = 0;
constexpr short constexpr_short = 0;

// print only last 3 address value numbers
const short addr_offset = 3;

// This function will print name, value and address for given parameter
void print_properties(std::string ref_name, const short* param, short offset)
{
    // determine initial size of strings
    std::string title = "value \\ address of ";
    const size_t ref_size = ref_name.size();
    const size_t title_size = title.size();
    assert(title_size > ref_size);

    // create title (resize)
    title.append(ref_name);
    title.append(" is ");
    title.append(title_size - ref_size, ' ');

    // extract last 'offset' values from address
    std::stringstream addr;
    addr << param;
    const std::string addr_str = addr.str();
    const size_t addr_size = addr_str.size();
    assert(addr_size - offset > 0);

    // print title / ref value / address at offset
    std::cout << title << *param << " " << addr_str.substr(addr_size - offset) << std::endl;
}

// here we test initialization of const variable (runtime)
void const_value(const short counter)
{
    static short temp = const_short;
    const short const_var = ++temp;
    print_properties("const", &const_var, addr_offset);

    if (counter)
        const_value(counter - 1);
}

// here we test initialization of static variable (runtime)
void static_value(const short counter)
{
    static short temp = const_short;
    static short static_var = ++temp;
    print_properties("static", &static_var, addr_offset);

    if (counter)
        static_value(counter - 1);
}

// here we test initialization of static const variable (runtime)
void static_const_value(const short counter)
{
    static short temp = const_short;
    static const short static_var = ++temp;
    print_properties("static const", &static_var, addr_offset);

    if (counter)
        static_const_value(counter - 1);
}

// here we test initialization of constexpr variable (compile time)
void constexpr_value(const short counter)
{
    constexpr short constexpr_var = constexpr_short;
    print_properties("constexpr", &constexpr_var, addr_offset);

    if (counter)
        constexpr_value(counter - 1);
}

// here we test initialization of static constexpr variable (compile time)
void static_constexpr_value(const short counter)
{
    static constexpr short static_constexpr_var = constexpr_short;
    print_properties("static constexpr", &static_constexpr_var, addr_offset);

    if (counter)
        static_constexpr_value(counter - 1);
}

// final test call this method from main()
void test_static_const()
{
    constexpr short counter = 2;

    const_value(counter);
    std::cout << std::endl;

    static_value(counter);
    std::cout << std::endl;

    static_const_value(counter);
    std::cout << std::endl;

    constexpr_value(counter);
    std::cout << std::endl;

    static_constexpr_value(counter);
    std::cout << std::endl;
}

正如您所见,
constepr
被多次初始化(地址不相同),而
static
关键字确保只执行一次初始化。

不制作大型数组
static
,即使是
constexpr
也会对性能产生巨大的影响,并可能导致许多优化失败。它可能会将代码的速度降低几个数量级。您的变量仍然是本地变量,编译器可能会决定在运行时初始化它们,而不是将它们作为数据存储在可执行文件中

考虑以下示例:

模板
void foo();
空栏(整数n)
{
//指向void(void)的四个函数指针数组
constexpr void(*表[])(void){
&福,
&福,
&福,
&福
};
//查找函数指针并调用它
表[n]();
}
您可能希望
gcc-10-O3
bar()
编译为
jmp
到它从表中获取的地址,但实际情况并非如此:

bar(int):
        mov     eax, OFFSET FLAT:_Z3fooILi0EEvv
        movsx   rdi, edi
        movq    xmm0, rax
        mov     eax, OFFSET FLAT:_Z3fooILi2EEvv
        movhps  xmm0, QWORD PTR .LC0[rip]
        movaps  XMMWORD PTR [rsp-40], xmm0
        movq    xmm0, rax
        movhps  xmm0, QWORD PTR .LC1[rip]
        movaps  XMMWORD PTR [rsp-24], xmm0
        jmp     [QWORD PTR [rsp-40+rdi*8]]
.LC0:
        .quad   void foo<1>()
.LC1:
        .quad   void foo<3>()
我们唯一的改变是我们制作了
静态
,但影响巨大:

bar(int):
movsx rdi,edi
jmp[QWORD PTR bar(int)::表[0+rdi*8]]
条形图(内部)::表格:
.foo()
.foo()
.foo()
.foo()

总之,永远不要使查找表成为局部变量,即使它们是
constexpr
。Clang实际上很好地优化了这样的查找表,但其他编译器没有

@AndrewLazarus,你不能从
const
对象中丢弃
const
,只能从指向
X
const X*
中丢弃。但这不是重点;关键是自动对象不能有静态地址。正如我所说的,
constexpr
一旦编译完成就不再有意义了,因此没有什么可以丢弃的了(很可能什么都没有,因为对象甚至不能保证在运行时存在)。我感觉这个答案不仅令人难以置信地困惑,而且还令人困惑
bar(int):
        mov     eax, OFFSET FLAT:_Z3fooILi0EEvv
        movsx   rdi, edi
        movq    xmm0, rax
        mov     eax, OFFSET FLAT:_Z3fooILi2EEvv
        movhps  xmm0, QWORD PTR .LC0[rip]
        movaps  XMMWORD PTR [rsp-40], xmm0
        movq    xmm0, rax
        movhps  xmm0, QWORD PTR .LC1[rip]
        movaps  XMMWORD PTR [rsp-24], xmm0
        jmp     [QWORD PTR [rsp-40+rdi*8]]
.LC0:
        .quad   void foo<1>()
.LC1:
        .quad   void foo<3>()