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