静态constexpr变量有意义吗?

时间:2012-12-13 18:08:40

标签: c++ static c++11 constexpr

如果我在函数内部有一个变量(比如一个大数组),那么将它staticconstexpr都声明是否有意义? constexpr保证数组是在编译时创建的,那么static也是无用的吗?

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

static实际上在生成的代码或语义方面做了什么吗?

4 个答案:

答案 0 :(得分:184)

简短的回答是,static不仅有用,而且总是很有希望。

首先,请注意staticconstexpr完全相互独立。 static定义对象在执行期间的生命周期; constexpr指定在编译期间对象应该可用。编辑和执行在时间和空间上都是不相交和不连续的。因此,一旦编译了程序,constexpr就不再相关了。

声明为constexpr的每个变量都隐含const,但conststatic几乎是正交的(除了与static const整数的交互。)

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

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

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

所以你绝对应该在你的例子中使用static constexpr

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

答案 1 :(得分:3)

除了给出的答案外,值得注意的是,编译器不需要在编译时初始化constexpr变量,因为知道constexprstatic constexpr之间的区别在于使用{ {1}}确保变量仅初始化一次。

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

此外,该代码将static constexprconstexpr结合使用时相对于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;
}

正如您所见,value \ address of const is 1 564 value \ address of const is 2 3D4 value \ address of const is 3 244 value \ address of static is 1 C58 value \ address of static is 1 C58 value \ address of static is 1 C58 value \ address of static const is 1 C64 value \ address of static const is 1 C64 value \ address of static const is 1 C64 value \ address of constexpr is 0 564 value \ address of constexpr is 0 3D4 value \ address of constexpr is 0 244 value \ address of static constexpr is 0 EA0 value \ address of static constexpr is 0 EA0 value \ address of static constexpr is 0 EA0 已多次初始化(地址不同),而constexpr关键字可确保初始化仅执行一次。

答案 2 :(得分:3)

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

考虑以下示例:

constexpr

您可能希望 template <int N> void foo(); void bar(int n) { // array of four function pointers to void(void) constexpr void(*table[])(void) { &foo<0>, &foo<1>, &foo<2>, &foo<3> }; // look up function pointer and call it table[n](); } gcc-10 -O3 编译为 bar() 到它从表中获取的地址,但事实并非如此:

jmp

这是因为 GCC 决定不在可执行文件的数据部分存储 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>() ,而是在每次函数运行时用其内容初始化一个局部变量。事实上,如果我们在这里删除 table,编译后的二进制文件是 100% 相同的。

这很容易比以下代码慢 10 倍:

constexpr

我们唯一的变化是我们做了 template <int N> void foo(); void bar(int n) { static constexpr void(*table[])(void) { &foo<0>, &foo<1>, &foo<2>, &foo<3> }; table[n](); } table,但影响是巨大的:

static

总而言之,永远不要将查找表设为局部变量,即使它们是 bar(int): movsx rdi, edi jmp [QWORD PTR bar(int)::table[0+rdi*8]] bar(int)::table: .quad void foo<0>() .quad void foo<1>() .quad void foo<2>() .quad void foo<3>() 。 Clang 实际上很好地优化了这样的查找表,但其他编译器没有。 See Compiler Explorer for a live example

答案 3 :(得分:0)

原来的问题仍然成立。不清楚为什么 constexpr 不能产生与 static constexpr 相同的结果,当编译器认为它更有效时。有人可以提供一个示例,说明用户需要非静态 constexpr 吗?

这可能是一个不同的问题,但我一直在寻找为什么在 static member 之前需要 constexpr。至少在 g++ 中,除非它在那里,否则它不会编译。恕我直言,这等效于声明单个枚举值(enum {FOO=1} 类似于 constexpr int FOO=1)并且枚举不需要 static