在以下c ++程序中:
struct O
{
const int id;
};
extern void externalFunc();
int func(O* o)
{
//first load of o->id happens here:
int first = o->id;
externalFunc();
//second load happens here, but why?
return o->id + first;
}
Clang和MSVC都对编译此代码进行了所有优化,其中o-> id值从内存加载两次。
为什么这些编译器无法删除第二个加载?我试图通过将id成员标记为const来告诉编译器保证不改变值,但显然两个编译器都没有找到足够的保证。如果我删除externalFunc()的调用,他们会优化第二次加载。我如何说服编译器这个值真的不会改变?
答案 0 :(得分:3)
答案 1 :(得分:2)
externalFunc()
可能会改变o->id
。 (不是o
,这是一个局部变量。)
答案 2 :(得分:1)
为什么这些编译器无法删除第二个加载?我试图通过将id成员标记为const来告诉编译器保证不改变值,但显然两个编译器都没有找到足够的保证。
因为不是。考虑这个例子。
static O mg {5};
void
externalFunc()
{
mg.~O();
new (&mg) O {6};
}
int
main()
{
std::cout << mg.id << '\n';
func(&mg);
std::cout << mg.id << '\n';
}
第一个加载读取值5,第二个加载读取6。
如何说服编译器这个值真的不会改变?
只需缓存该字段即可。这仍然不能说服编译器o->id
不会改变,但它会确保如果确实如此,你就不在乎了。
int
func(O* o)
{
const int id = o->id;
externalFunc();
return id + id;
}
我已经习惯将通过指针(包括this
指针)访问的原始字段的所有值缓存到本地(const
)变量中。如果编译器可以确保值不能更改,则无需额外成本,如果不能,则可能会产生稍微好一点的代码。另外,它还允许您为函数上下文中最有意义的值指定名称。
答案 3 :(得分:0)
编译器本身在编译externalFunc()
时没有func()
的代码,所以它不知道它可能做什么。因此,它起到了障碍的作用。
如果您静态链接,这将属于链接时优化(可以使用-flto
在GCC上启用),并且在MSVC中也受支持。
为了暗示GCC / Clang(看起来像MSVC不支持这个),该函数不会改变全局内存,你可以用pure
属性标记它:
extern void __attribute__((pure)) void externalFunc();
然后它会停止构成这个障碍。