当前位置: 首页 > 知识库问答 >
问题:

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

杜起运
2023-03-14

如果我在函数中有一个变量(例如,一个大数组),那么同时声明<code>static</code>和<code>constexpr</code>有意义吗constexpr保证数组是在编译时创建的,所以static是否无用?

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

静态实际上是否在生成的代码或语义方面做了什么?

共有3个答案

羊煜
2023-03-14
匿名用户

不使大型数组静态,即使它们是 constexpr 也会对性能产生巨大影响,并可能导致许多优化失误。它可能会使您的代码速度减慢几个数量级。变量仍然是局部变量,编译器可能会决定在运行时初始化它们,而不是将它们作为数据html" target="_blank">存储在可执行文件中。

考虑以下示例:

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-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>()

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

这很容易比以下代码慢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]();
}

我们唯一的改变是我们将< code > table < code > 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实际上很好地优化了这样的查找表,但其他编译器没有。有关实时示例,请参阅编译器资源管理器。

茅曾琪
2023-03-14

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

下面的代码演示了如何多次初始化的变量(虽然具有相同的值),而的静态配置肯定只初始化一次。

此外,代码将的优势与const静态相结合进行了比较。

#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

正如您自己所看到的,就像被多次初始化(地址不一样),而静态关键字确保初始化只执行一次。

司宏伯
2023-03-14

简单的答案是,static不仅有用,而且它总是很有用的。

首先,注意< code>static和< code>constexpr是完全相互独立的。< code>static定义对象在执行期间的生存期;< code>constexpr指定对象在编译期间应该可用。编译和执行在时间和空间上都是不相交和不连续的。所以一旦程序被编译,< code>constexpr就不再相关了。

每个声明为<code>constexpr</code>的变量都是隐式的<code>常量</code<,但<code>const</code>和<code>static</coode>几乎是正交的(除了与<code>静态常量</code>整数的交互作用)

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

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

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

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

但是,在某种情况下,您不希望使用静态配置文件。除非声明的对象是ODR使用的或声明的静态配置文件,否则编译器可以自由地根本不包含它。这非常有用,因为它允许使用编译时临时配置文件数组,而不会用不必要的字节污染编译程序。在这种情况下,您显然不想使用静态配置文件,因为静态配置文件可能会强制对象在运行时存在。

 类似资料:
  • 实际代码更复杂,但我能够将其简化为这个示例。 在我尝试获取指向MyPackets\u t::type的指针(在main()中取消对foo()的注释调用)之前,一切都正常 此时,为了使应用程序链接,类型需要定义。 我正在努力寻找正确的定义语法。已注释掉模板。。。应该做到这一点。但是,它生成了一个错误“PacketCollection::types的模板参数与原始模板不匹配”。 尝试这样的东西-模板

  • 我只是想学习Angular 2(特别是Angular 8),就我的一生而言,我不明白为什么类变量在类函数中是“未定义”的,但如果我用ES6风格编写函数,它是可以访问的。 我尝试在构造函数中设置,但这没有意义。 一旦调用HandleClickStart,每1.5秒输出一次NaN。为什么????我本以为是1 2 3...... 通过这种方式实现handleClickStart,可以获得预期的结果: 但

  • 问题内容: 我试图弄清楚如何声明一个静态变量,其范围仅限于Swift中的函数。 在C中,这可能看起来像这样: 在Objective-C中,基本上是相同的: 但是我似乎无法在Swift中做这样的事情。我尝试通过以下方式声明变量: 但是这些都会导致错误。 第一个抱怨“静态属性只能在类型上声明”。 第二个抱怨“期望的声明”(在哪里)和“期望的模式”(在哪里) 第三条抱怨“一行上的连续语句必须用’;’分隔

  • 问题内容: 我对Java还是陌生的,我尝试创建一个内部类并在main内部调用该方法。但是出现编译错误,提示“非静态变量-不能从静态上下文中引用” 请帮忙 问题答案: 一个 内部 类需要的的一个实例的引用 外 类以构造。如果您的类在逻辑上不需要它,请使用修饰符将其设置为“仅嵌套类”: 编辑:要创建一个作为 内部 类的实例,您可以使用类似以下内容的方法: 或更简短地说: …但是除非您真的 想要 引用封

  • 问题内容: 我已经定义了一个对象并声明了一个静态变量。在该方法中,当我尝试打印实例和类变量时,两者都打印相同的值。 不是实例变量吗?它应该打印0而不是50吗? 问题答案: 不,只有一个变量-您尚未声明任何实例变量。 不幸的是,Java允许您访问静态成员,就像通过相关类型的引用访问静态成员一样。这是IMO的设计缺陷,某些IDE(例如Eclipse)允许您将其标记为警告或错误- 但这是语言的一部分。您

  • 我还是Java新手,我试图创建一个内部类并在main中调用该方法。有一个编译错误说 非静态变量 - 这不能从静态上下文中引用 请帮忙