简体   繁体   English

函数内的静态 constexpr 变量有意义吗?

[英]Does static constexpr variable inside a function make sense?

If I have a variable inside a function (say, a large array), does it make sense to declare it both static and constexpr ?如果我在一个函数中有一个变量(比如一个大数组),将它同时声明为staticconstexpr是否有意义? constexpr guarantees that the array is created at compile time, so would the static be useless? constexpr保证数组是在编译时创建的,那么static会无用吗?

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

Is the static actually doing anything there in terms of generated code or semantics?就生成的代码或语义而言, static实际上是否在做任何事情?

The short answer is that not only is static useful, it is pretty well always going to be desired.简短的回答是, static不仅有用,而且总是很受欢迎。

First, note that static and constexpr are completely independent of each other.首先,注意staticconstexpr是完全独立的。 static defines the object's lifetime during execution; static定义了对象在执行期间的生命周期; constexpr specifies that the object should be available during compilation. constexpr指定对象在编译期间应该可用。 Compilation and execution are disjoint and discontiguous, both in time and space.编译和执行在时间和空间上都是不相交和不连续的。 So once the program is compiled, constexpr is no longer relevant.所以一旦程序被编译, constexpr就不再相关了。

Every variable declared constexpr is implicitly const but const and static are almost orthogonal (except for the interaction with static const integers.)声明为constexpr每个变量都是隐式constconststatic几乎是正交的(除了与static const整数的交互。)

The C++ object model (§1.9) requires that all objects other than bit-fields occupy at least one byte of memory and have addresses; C++对象模型(第 1.9 节)要求除位域之外的所有对象至少占用一个字节的内存并具有地址; furthermore all such objects observable in a program at a given moment must have distinct addresses (paragraph 6).此外,在给定时刻在程序中可观察到的所有此类对象必须具有不同的地址(第 6 段)。 This does not quite require the compiler to create a new array on the stack for every invocation of a function with a local non-static const array, because the compiler could take refuge in the as-if principle provided it can prove that no other such object can be observed.这并不完全要求编译器在每次调用具有本地非静态 const 数组的函数时都在堆栈上创建一个新数组,因为编译器可以求助于as-if原则,前提是它可以证明没有其他此类可以观察物体。

That's not going to be easy to prove, unfortunately, unless the function is trivial (for example, it does not call any other function whose body is not visible within the translation unit) because arrays, more or less by definition, are addresses.不幸的是,这并不容易证明,除非该函数是微不足道的(例如,它不调用其主体在翻译单元中不可见的任何其他函数),因为数组或多或少根据定义是地址。 So in most cases, the non-static const(expr) array will have to be recreated on the stack at every invocation, which defeats the point of being able to compute it at compile time.因此,在大多数情况下,每次调用时都必须在堆栈上重新创建非静态const(expr)数组,这违背了能够在编译时计算它的意义。

On the other hand, a local static const object is shared by all observers, and furthermore may be initialized even if the function it is defined in is never called.另一方面,局部static const对象被所有观察者共享,而且即使定义它的函数从未被调用,也可能被初始化。 So none of the above applies, and a compiler is free not only to generate only a single instance of it;所以以上都不适用,编译器不仅可以自由地生成它的单个实例; it is free to generate a single instance of it in read-only storage.可以在只读存储中免费生成它的单个实例。

So you should definitely use static constexpr in your example.所以你绝对应该在你的例子中使用static constexpr

However, there is one case where you wouldn't want to use static constexpr .但是,在一种情况下,您不想使用static constexpr Unless a constexpr declared object is either ODR-used or declared static , the compiler is free to not include it at all.除非constexpr声明的对象是ODR 使用的或声明为static ,否则编译器可以完全不包含它。 That's pretty useful, because it allows the use of compile-time temporary constexpr arrays without polluting the compiled program with unnecessary bytes.这非常有用,因为它允许使用编译时临时constexpr数组,而不会用不必要的字节污染已编译的程序。 In that case, you would clearly not want to use static , since static is likely to force the object to exist at runtime.在这种情况下,您显然不想使用static ,因为static可能会强制对象在运行时存在。

In addition to given answer, it's worth noting that compiler is not required to initialize constexpr variable at compile time, knowing that the difference between constexpr and static constexpr is that to use static constexpr you ensure the variable is initialized only once.除了给出的答案之外,值得注意的是,编译器不需要在编译时初始化constexpr变量,知道constexprstatic constexpr之间的区别在于使用static constexpr您确保变量只初始化一次。

Following code demonstrates how constexpr variable is initialized multiple times (with same value though), while static constexpr is surely initialized only once.以下代码演示了如何多次初始化constexpr变量(尽管具有相同的值),而static constexpr肯定只初始化一次。

In addition the code compares the advantage of constexpr against const in combination with static .此外,代码将constexprconst结合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;
}

Possible program output:可能的程序输出:

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

As you can see yourself constexpr is initilized multiple times (address is not the same) while static keyword ensures that initialization is performed only once.正如您所看到的, constexpr被初始化多次(地址不相同),而static关键字确保初始化只执行一次。

Not making large arrays static , even when they're constexpr can have dramatic performance impact and can lead to many missed optimizations.不使大型数组static ,即使它们是constexpr也会产生巨大的性能影响,并可能导致许多优化错​​过。 It may slow down your code by orders of magnitude.它可能会按数量级减慢您的代码。 Your variables are still local and the compiler may decide to initialize them at runtime instead of storing them as data in the executable.您的变量仍然是本地的,编译器可能会决定在运行时初始化它们,而不是将它们作为数据存储在可执行文件中。

Consider the following example:考虑以下示例:

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]();
}

You probably expect gcc-10 -O3 to compile bar() to a jmp to an address which it fetches from a table, but that is not what happens:您可能希望gcc-10 -O3bar()编译为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>()

This is because GCC decides not to store table in the executable's data section, but instead initializes a local variable with its contents every time the function runs.这是因为 GCC 决定不在可执行文件的数据部分中存储table ,而是在每次函数运行时用其内容初始化一个局部变量。 In fact, if we remove constexpr here, the compiled binary is 100% identical.事实上,如果我们在这里删除constexpr ,编译后的二进制文件是 100% 相同的。

This can easily be 10x slower than the following code:这很容易比以下代码慢 10 倍:

template <int N>
void foo();

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

Our only change is that we have made table static , but the impact is enormous:我们唯一的变化是我们使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>()

In conclusion, never make your lookup tables local variables, even if they're constexpr .总之,永远不要让你的查找表成为局部变量,即使它们是constexpr Clang actually optimizes such lookup tables well, but other compilers don't. Clang 实际上很好地优化了这样的查找表,但其他编译器没有。 See Compiler Explorer for a live example .请参阅编译器资源管理器以获取实时示例

The original question still stands.最初的问题仍然成立。 It is unclear why it cannot be a rule that constexpr can produce the same result as static constexpr , when the compiler believes it is more efficient.尚不清楚为什么当编译器认为static constexpr更有效时, constexpr不能产生与static constexpr相同的结果的规则。 Can somebody provide an example where the user requires the non-static constexpr ?有人可以提供一个用户需要非静态constexpr的例子吗?

It's possibly a different question, but I was looking for why static is required before a constexpr member of a class.这可能是一个不同的问题,但我一直在寻找为什么在类的constexpr成员之前需要static At least in g++ it does not compile unless it is there.至少在 g++ 中,除非它在那里,否则它不会编译。 IMHO this is pretty equivalent to declaring a single enumeration value ( enum {FOO=1} is similar to constexpr int FOO=1 ) and no static is needed for the enum.恕我直言,这相当于声明一个枚举值( enum {FOO=1}类似于constexpr int FOO=1 )并且枚举不需要static

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM