This is related to Why can't GCC generate an optimal operator== for a struct of two int32s?. I was playing around with the code from that question at godbolt.org and noticed this odd behavior.
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;
}
For nonzero_ptr, clang -O3 (all versions) produces this or similar code:
mov al, 1
cmp dword ptr [rdi], 0
je .LBB0_1
ret
.LBB0_1:
cmp dword ptr [rdi + 4], 0
setne al
ret
This strictly implements the short-circuiting behavior of the C++ function, loading the y field only if the x field is zero.
For nonzero_ref, clang 3.6 and earlier generate the same code as they do for nonzero_ptr, but clang 3.7 through 11.0.1 produce
mov eax, dword ptr [rdi + 4]
or eax, dword ptr [rdi]
setne al
ret
which loads y unconditionally. No version of clang is willing to do that when the parameter is a pointer. Why?
The only situation I can think of (on the x64 platform) where the behavior of the branching code would be observably different is when there's no memory mapped at [rdi+4], but I'm still unsure why clang would consider that case important for pointers and not references. My best guess is that there is some language-lawyery argument that references must be to "full objects" and pointers needn't be:
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???
But if the spec implies that, I'm not seeing how.