3 ms·
This is entirely wrong. Taking the address of a field doesn't evaluate the field, and any compiler that does a load for that is broken.
by jwatte 12y ago
This is entirely wrong. Taking the address of a field doesn't evaluate the field, and any compiler that does a load for that is broken.
- pcwalton 12y agoWhere it kicks in is code like this: ... &p[1].foo ... for (int i = 0; i < ...; i++) { bar += p[0].foo; } The compiler wants to transform the loop as follows: auto tmp = p[0].foo; for (int i = 0; i < ...; i++) { bar += tmp; } Assume that the compiler cannot prove that the loop will execute at least once. Now observe that this transformation is correct if and only if the pointer p is dereferenceable (with the offset of the field foo applied). The optimization would miscompile a perfectly valid program if p were NULL and the loop ran zero times. And because of strict-aliasing rules, the only way that p would not be dereferenceable in this way is if it were NULL or one element beyond the end of an array. So the compiler wants to prove that p is not NULL upon entry to the loop. And in this program, it can--because of the address computation, the behavior is undefined if p is NULL or one element beyond the end of an array. Therefore, the compiler can safely assume that p is not NULL and perform the loop invariant code motion. In this program, it can only do so thanks to the rule about undefined behavior in address computations. Note that I'm not aware of any compiler that actually does this, because the C++ standard committee seems to consider this undefined behavior an oversight in the spec (per the discussion linked there). But the OP successfully argues that it could, per a lawyer-y reading of the spec. Edit: Argh, I missed the case in which p was one element beyond the end of an array. Updated.
- nkurz 12y agoI like your detailed example, but wouldn't a slightly kinder but equally optimizing compiler simply add a conditional before the load: if (max > 0) { int i = max; int tmp = p[0].foo; do { bar += tmp; } while (--i); } I'd even argue the assembly is slightly better when interpreted this way, since branch prediction will tend execute the load or not as appropriate, rather than potentially taking a unnecessary TLB miss for a load that's never used.