这与Why can't GCC generate an optimal operator== for a struct of two int32s?相关。我在godbolt.org上玩弄这个问题的代码时,注意到了这个奇怪的行为。
struct Point {
int x, y;
};
bool nonzero_ptr(Point const* a) {
return a->x || a->y;
}
bool nonzero_ref(Point const& a) {
return a.x || a.y;
}对于nonzero_ptr,clang -O3 (所有版本)会生成以下或类似的代码:
mov al, 1
cmp dword ptr [rdi], 0
je .LBB0_1
ret
.LBB0_1:
cmp dword ptr [rdi + 4], 0
setne al
ret这严格实现了C++函数的短路行为,仅当x字段为零时才加载y字段。
对于nonzero_ref,Clang3.6和更早版本会生成与nonzero_ptr相同代码,但Clang3.7到11.0.1会生成
mov eax, dword ptr [rdi + 4]
or eax, dword ptr [rdi]
setne al
ret它会无条件地加载y。当参数是指针时,任何版本的clang都不愿意这样做。为什么?
我能想到的(在x64平台上)分支代码行为明显不同的唯一情况是在[rdi+4]没有内存映射的情况下,但我仍然不确定为什么clang会认为这种情况对于指针而不是引用很重要。我最好的猜测是,有一些语言律师的论点,引用必须是“完整的对象”,而指针不必是:
char* p = alloc_4k_page_surrounded_by_guard_pages();
int* pi = reinterpret_cast<int*>(p + 4096 - sizeof(int));
Point* ppt = reinterpret_cast<Point*>(pi); // ok???
ppt->x = 42; // ok???
Point& rpt = *ppt; // UB???但如果规范中暗示了这一点,我就看不出怎么做了。
发布于 2021-02-21 13:43:35
我相信从标准C++的角度来看,编译器可以为两者生成相同的代码,因为标准中没有像您构建的那样的“部分对象”的规定。事实上,它不是一个简单的优化错过了。
可以比较像a->x || b->y这样的代码,编译器确实必须发出分支,因为只要a->x为非零,调用者就可以合法地为b传递空或无效的指针。另一方面,如果a、b是引用,那么根据标准,a.x || b.y应该不需要分支,因为它们必须始终是对有效对象的引用。因此,nonzero_ptr中的“遗漏优化”可能只是编译器没有注意到它可以利用a->x和a->y中的指针是同一个指针这一事实。
或者,clang作为一个扩展,可能会试图生成代码,当您使用非标准功能创建只有一些成员可以访问的对象时,这些代码仍然可以工作。事实上,这只适用于指针,而不适用于引用,这可能是该扩展的一个错误或限制,但我不认为这是任何类型的一致性违规。
https://stackoverflow.com/questions/66298438
复制相似问题