Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Which of these pointer comparisons should a conforming compiler be able to optimize to "always false"?

In an attempt to get a better understand of how pointer aliasing invariants manifested during optimization, I plugged some code into the renowned Compiler Explorer, which I'll repeat here:

#include <cstring>

bool a(int *foo, int *bar) {
    (void) *foo, (void) *bar;
    return foo == bar;
}

bool b(int *foo, float *bar) {
    (void) *foo, (void) *bar;
    return foo == reinterpret_cast<int *>(bar);
}

bool c(int *foo, int *bar) {
    (void) *foo, (void) *bar;
    // It's undefined behavior for memcpyed memory ranges to overlap (i.e. alias)
    std::memcpy(foo, bar, sizeof(int));
    return foo == bar;
}

bool d(int *__restrict foo, int *__restrict bar) {
    (void) *foo, (void) *bar;
    return foo == bar;
}

Neither the current versions of Clang nor GCC compile any of these functions to always return false, so my question is then which of these functions, while still complying with the C++ standard, could have been compiled to always return false? My (very limited) understanding says b, c, and d should all be optimizable in that manner, but I'm not confident (I also recognize that __restrict isn't in the standard, but pretending that it was with the semantics it's defined to have under either compiler).

Update

I've included dereferences of both pointers in the top of each function (so that they cannot be nullptr), and made the std::memcpy invocation actually copy one instance of int.

Update 2

Added a comment explaining my intent with the std::memcpy.

like image 532
lcmylin Avatar asked May 12 '19 15:05

lcmylin


1 Answers

For a it is obvious. For b the code is actually correct, the compiler cannot make any assumptions. Consider this call to b:

int x[2]{};
b(x,reinterpret_cast<float*>(x+1));

If you were accessing the value of the two parameters, maybe the compiler could make assumptions:

bool b(int *foo, float *bar) {
    *foo=10;  //*foo is an int (or unsigned int) 
              //and if foo is a member of a union 
              //*foo is the active member
    *bar+0.f; //bar must be a float within its lifetime so it cannot be
              //in the same union as *foo
    return foo == reinterpret_cast<int *>(bar);//so always false
    }

For c I agree with your analyze, a very smart compiler could optimize away the comparison.

For d, according to the C standard restrict only has implication on the way an object is accessed, not on the value of pointers see §6.7.3 in N1570

An object that is accessed through a restrict-qualified pointer has a special association with that pointer. This association, defined in 6.7.3.1 below, requires that all accesses to that object use, directly or indirectly, the value of that particular pointer.

As in the case of b if pointed object were accessed then a smart compiler could make assumptions:

bool d(int *__restrict foo, int *__restrict bar) {
  *foo=10;
  *bar=12;//So foo and bar point to different objects
  return foo == bar;//always false
  }
like image 67
Oliv Avatar answered Nov 17 '22 07:11

Oliv