静态constexpr变量有意义吗?

Dav*_*one 172 c++ static constexpr c++11

如果我有一个函数(比方说,一个大阵列)内的变量,它是有意义的声明它既staticconstexprconstexpr保证数组是在编译时创建的,那么它会static没用吗?

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

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

ric*_*ici 207

简短的回答是,不仅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使用的或声明的static,否则编译器可以根本不包含它.这非常有用,因为它允许使用编译时临时constexpr数组,而不会用不必要的字节来污染编译的程序.在这种情况下,您显然不想使用static,因为static可能会强制对象在运行时存在.

  • 我有点觉得这个答案不仅令人难以置信地混乱,而且还有自相矛盾.例如,你说你几乎总是想要`static`和`constexpr`,但要解释它们是正交的和独立的,做不同的事情.然后你提到一个不合并两者的理由,因为它会忽略ODR使用(这似乎很有用).哦,我仍然不明白为什么静态应该与constexpr一起使用,因为静态是运行时的东西.你从未解释过为什么静态与constexpr很重要. (11认同)
  • 提及编译时常量与运行时常量可能也很有用.换句话说,如果`constexpr`常量变量仅在编译时上下文中使用而在运行时从不需要,那么`static`没有任何意义,因为到达运行时,该值已被有效地"内联" ".但是,如果在运行时上下文中使用`constexpr`(换句话说,`constexpr`需要隐式转换为`const`,并且可用于运行时代码的物理地址),它将需要`static`来确保ODR合规等等,这至少是我的理解. (6认同)
  • @AndrewLazarus,您不能仅从指向“ X”的“ const X *”中丢弃“ const”对象中的“ const”。但这不是重点。关键是自动对象不能具有静态地址。就像我说过的那样,一旦编译完成,`constexpr`就不再有意义了,因此没有什么要抛弃的(可能根本就什么也没有,因为甚至不能保证对象在运行时也存在)。 (2认同)
  • @ void.pointer:您对最后一段是正确的。我更改了简介。我以为我已经解释了“静态constexpr”的重要性(它可以防止在每次函数调用时都必须重新创建常量数组),但是我调整了一些词以使其更加清晰。谢谢。 (2认同)
  • 我最后一个评论的示例:`static constexpr int foo = 100;`。除非代码做类似&foo的事情,否则编译器没有理由不能在各处用foo代替原义的100。因此,在这种情况下,在foo上使用static并没有用,因为在运行时不存在foo。再次全部取决于编译器。 (2认同)

Jan*_*tke 27

不创建大型数组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]();
}
Run Code Online (Sandbox Code Playgroud)

您可能希望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>()
Run Code Online (Sandbox Code Playgroud)

这是因为 GCC 决定不存储table在可执行文件的数据部分中,而是在每次函数运行时用其内容初始化局部变量。事实上,如果我们删除constexpr此处,编译后的二进制文件是 100% 相同的。

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

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>()
Run Code Online (Sandbox Code Playgroud)

我们唯一的改变是我们所做的table static,但影响是巨大的:

template <int N>
void foo();

void bar(int n)
{
    static constexpr void(*table[])(void) {
        &foo<0>,
        &foo<1>,
        &foo<2>,
        &foo<3>
    };
    table[n]();
}
Run Code Online (Sandbox Code Playgroud)

总之,永远不要将查找表设为局部变量,即使它们是constexpr. Clang 实际上很好地优化了此类查找表,但其他编译器则不然。有关实例,请参阅编译器资源管理器


met*_*ter 15

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

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

此外,代码比较了constexpragainstconststatic.

#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;
}
Run Code Online (Sandbox Code Playgroud)

可能的程序输出:

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
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,你自己constexprstatic初始化了多次(地址不一样),而关键字确保初始化只执行一次。

  • @metablaster 这并不是说“char”是“特殊的野兽”。`constexpr char*` 只是将 `const` 应用于指针本身,而不是指针类型。它是“char * const”与“const char*”。这种语法差异适用于任何指针类型。 (5认同)