POD表示没有构造函数和析构函数的原始数据类型。
我很好奇,编译器如何处理POD静态局部变量的延迟初始化。如果函数要在多线程应用程序中的紧密循环中运行,那么延迟初始化的含义是什么?这些是可能的选择。哪一个更好?
void foo_1() {
static const int v[4] = {1, 2, 3, 4};
}
void foo_2() {
const int v[4] = {1, 2, 3, 4};
}
这个怎么样?没有延迟初始化,但语法略显笨拙?
struct Bar
{
static const int v[4];
void foo_3()
{
// do something
}
};
const int My::v[4] = {1, 2, 3, 4};
答案 0 :(得分:4)
当使用常量数据初始化静态变量时,我熟悉的所有编译器都会在编译时初始化值,这样就不会产生任何运行时开销。
如果变量不是静态的,则必须在每次函数调用时分配,并且必须将值复制到其中。我想如果它是一个const变量,编译器可能会将它优化为静态,除了const-ness可以被抛弃。
答案 1 :(得分:2)
在foo_1()
中,v
在main()
开始前的某个时间初始化。在foo_2()
中,每次调用v
时都会创建并初始化foo_2()
。使用foo_1()
消除额外费用。
在第二个示例中,Bar::v
也在main()
之前的某个时间初始化。
答案 2 :(得分:2)
性能比分配更复杂。例如,您可能导致额外的缓存行必须与静态变量一起位于缓存中,因为它与您正在使用的其他本地内存不相邻,并且会增加缓存压力,缓存未命中等等。与此成本相比,我会说每次在堆栈上重新分配数组的极其微小的开销将是非常微不足道的。不仅如此,但任何编译器都非常善于优化这类事情,而它无法对静态变量做任何事情。
在任何情况下,我都会建议两者之间的性能差异很小 - 即使是在紧密循环内部。
最后,您也可以使用foo_2() - 编译器完全有权使用这样的静态变量。因为它最初定义为const,const_casting const away是未定义的行为,无论它是否是静态的。但是,它不能选择使静态常量非静态,例如,您可能依赖于返回其地址的能力。
答案 3 :(得分:1)
找出变量初始化方式的简单方法是打印具有静态和局部变量的函数的汇编语言列表。
并非所有编译器都在同一方法中初始化变量。这是一种常见的做法:
在main()
方法之前,通过将一部分值复制到变量中来初始化全局变量。许多编译器会将常量放入一个区域,以便可以使用简单的装配移动或复制指令来分配数据。
本地变量(具有本地范围的变量)可以在进入本地范围时以及在执行范围中的第一个语句之前初始化。这取决于许多因素,其中之一是变量的const
。
常量可以直接放在可执行代码中,也可以是指向ROM中的值的指针,也可以复制到内存或寄存器中。这取决于编译器的设置,由编译器决定最佳性能或代码大小。
答案 4 :(得分:1)
在技术方面,在任何函数(包括类构造函数)被调用之前,需要foo_1
和foo_3
来初始化它们的数组。这种保证基本上与没有运行时一样好。实际上,大多数实现都不需要任何运行时来初始化它们。
此保证仅适用于具有静态存储持续时间的POD类型的对象,这些对象使用“常量表达式”初始化。一些对比鲜明的例子:
void foo_4() {
static const int v[4] = { firstv(), 2, 3, 4 };
}
namespace { // anonymous
const int foo_5_data[4] = { firstv(), 2, 3, 4 };
}
void foo_5() {
const int (&v)[4] = foo_5_data;
}
foo_4
的数据在第一次调用foo_4
时初始化。 (检查编译器文档以确定这是否是线程安全的!)
foo_5
的数据在main()
之前的某个时间初始化,但可能在其他一些动态初始化之后。
但这些都没有真正回答关于表现的问题,而且我没有资格对此发表评论。 @ DeadMG的答案看起来很有帮助。
答案 5 :(得分:1)
在所有这些情况下都有静态初始化,所有静态变量都将通过将数据段加载到内存中来初始化。如果编译器发现可能,则可以初始化foo_2中的const。 如果您有动态初始化,那么命名空间作用域中的变量初始化可以推迟到第一次使用时。类似地,可以在第一次通过函数或更早的时间期间执行函数范围中的局部静态变量的动态初始化。此外,如果能够执行此操作,编译器可以静态初始化这些变量。我不记得标准的确切措辞。