在运行时初始化成员变量而不引用或使用它会进一步占用RAM,还是编译器只是忽略该变量?
struct Foo {
int var1;
int var2;
Foo() { var1 = 5; std::cout << var1; }
};
在上面的示例中,成员'var1'得到一个值,该值然后显示在控制台中。但是,根本不使用“ Var2”。因此,在运行时将其写入内存将浪费资源。编译器会考虑这种情况,而只是忽略未使用的变量,还是Foo对象总是相同大小,而不管其成员是否被使用?
答案 0 :(得分:104)
金色的C ++“假设”规则 1 指出,如果程序的observable behavior不依赖于未使用的数据成员,则编译器可以对其进行优化。
未使用的成员变量会占用内存吗?
否(如果“确实”未使用)。
现在要记住两个问题:
让我们从一个例子开始。
#include <iostream>
struct Foo1
{ int var1 = 5; Foo1() { std::cout << var1; } };
struct Foo2
{ int var1 = 5; int var2; Foo2() { std::cout << var1; } };
void f1() { (void) Foo1{}; }
void f2() { (void) Foo2{}; }
如果我们问gcc to compile this translation unit,它将输出:
f1():
mov esi, 5
mov edi, OFFSET FLAT:_ZSt4cout
jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
f2():
jmp f1()
f2
与f1
相同,并且没有内存用于存放实际的Foo2::var2
。 (Clang does something similar)。
有人可能说这有所不同,原因有两个:
好的,一个好的程序是简单事物的智能复杂组合,而不是复杂事物的简单并置。在现实生活中,您使用简单的结构编写了大量的简单函数,而编译器无法优化这些简单函数。例如:
bool insert(std::set<int>& set, int value)
{
return set.insert(value).second;
}
这是未使用数据成员(此处为std::pair<std::set<int>::iterator, bool>::first
)的真实示例。你猜怎么了? It is optimized away(simpler example with a dummy set,如果那个集会让你哭泣)。
现在将是read the excellent answer of Max Langhof的最佳时机(请为我投票)。最终解释了为什么结构的概念在编译器输出的汇编级别没有意义。
有很多评论认为此答案一定是错误的,因为某些操作(例如assert(sizeof(Foo2) == 2*sizeof(int))
)会破坏某些内容。
如果X是程序 2 的可观察行为的一部分,则不允许编译器优化这些内容。对包含“未使用”数据成员的对象有很多操作,这将对程序产生明显影响。如果执行了这样的操作,或者如果编译器无法证明没有执行任何操作,则“未使用”的数据成员是程序可观察到的行为的一部分,无法优化。
影响可观察行为的操作包括但不限于:
sizeof(Foo)
)的大小,memcpy
之类的功能复制对象memcmp
)1)
本文档中的语义描述定义了参数化的不确定性抽象机。本文档对符合实现的结构没有任何要求。特别是,它们不需要复制或模拟抽象机的结构。相反,需要遵循一致的实现来(仅)模拟抽象机的可观察行为,如下所述。
2)就像断言是通过还是失败。
答案 1 :(得分:61)
重要的是要认识到编译器生成的代码对您的数据结构没有实际的了解(因为这样的东西在汇编级别上不存在),优化器也没有。编译器仅为每个函数生成 code ,而不为数据结构生成。
好,它还会写入常量数据段等。
基于此,我们已经可以说优化器不会“删除”或“消除”成员,因为它不会输出数据结构。它输出 code ,可能会或可能不会使用成员,其目标之一是通过消除毫无意义的 uses (即写操作)来节省内存或周期/ reads)。
其要点是,“如果编译器可以证明在功能范围内的 (包括内联到其中的功能),则未使用的成员对功能的运行方式没有任何影响(以及返回的内容),那么该成员的存在很可能不会造成开销”。
当您使函数与外界的交互对于编译器而言更加复杂/不清楚(采用/返回更复杂的数据结构,例如std::vector<Foo>
时,将函数的定义隐藏在不同的编译单元中) ,禁止/取消内联等),编译器无法证明未使用的成员无效的可能性越来越大。
这里没有硬性规则,因为这完全取决于编译器所做的优化,但是,只要您做一些琐碎的事情(例如YSC的答案中所示),就很可能不会出现开销,而做复杂的事情(例如,从无法进行内联的函数返回std::vector<Foo>
可能会产生开销。
为说明这一点,请考虑this example:
struct Foo {
int var1 = 3;
int var2 = 4;
int var3 = 5;
};
int test()
{
Foo foo;
std::array<char, sizeof(Foo)> arr;
std::memcpy(&arr, &foo, sizeof(Foo));
return arr[0] + arr[4];
}
我们在这里做了一些琐碎的事情(获取地址,检查并添加byte representation中的字节),但是优化器可以确定在该平台上结果始终是相同的:
test(): # @test()
mov eax, 7
ret
不仅Foo
的成员不占用任何内存,Foo
甚至不存在!如果还有其他无法优化的用法,例如sizeof(Foo)
可能很重要-但仅适用于该段代码!如果可以像这样优化所有用法,则存在var3
不会影响生成的代码。但是,即使在其他地方使用它,test()
也会保持最佳状态!
简而言之: Foo
的每次使用都是独立优化的。由于不需要成员,有些可能会使用更多的内存,有些可能不会。有关更多详细信息,请查阅您的编译器手册。
答案 2 :(得分:21)
如果编译器可以证明删除变量没有副作用,并且程序的任何部分都不依赖于Foo
的大小,则编译器只会优化未使用的成员变量(尤其是公共变量)。一样。
除非完全没有真正使用该结构,否则我认为任何当前的编译器都不会执行此类优化。一些编译器可能至少会警告未使用的私有变量,但通常不会警告公共变量。
答案 3 :(得分:6)
此问题的其他答案提供的示例中,忽略var2
的示例是基于一种优化技术的:恒定传播和随后整个结构的省略(不只是var2
的省略)。这是简单的情况,优化编译器可以实现它。
对于非托管C / C ++代码,答案是编译器通常不会忽略var2
。据我所知,调试信息中不支持这种C / C ++结构转换,并且如果该结构可以在调试器中作为变量访问,那么就不能忽略var2
。据我所知,当前的C / C ++编译器无法根据var2
的含义来专门化函数,因此,如果将结构传递给非内联函数或从非内联函数返回,则无法忽略var2
。 / p>
对于使用JIT编译器的托管语言(例如C#/ Java),编译器可能能够安全地退出var2
,因为它可以精确跟踪是否正在使用它以及是否转义为非托管代码。托管语言中的结构的物理大小可以与报告给程序员的大小不同。
2019年C / C ++编译器无法从结构中忽略var2
,除非整个struct变量都被删除了。对于从结构中删除var2
的有趣情况,答案是:否。
某些将来的C / C ++编译器将能够从结构中剔除var2
,围绕编译器构建的生态系统将需要适应编译器生成的处理省略信息。
答案 4 :(得分:4)
这取决于您的编译器及其优化级别。
在gcc中,如果您指定loadChildren: () => ModuleName
,它将打开following optimization flags:
-O
-fauto-inc-dec
-fbranch-count-reg
-fcombine-stack-adjustments
-fcompare-elim
-fcprop-registers
-fdce
-fdefer-pop
...
代表Dead Code Elimination。
您可以使用-fdce
来防止gcc通过静态存储消除未使用的变量:
此属性附加到具有静态存储空间的变量中,表示 即使该变量似乎是 未引用。
当应用于C ++类模板的静态数据成员时, 属性还意味着,如果该类被实例化,则该成员 本身被实例化。