54

Look at this simple code:

struct Point {
    int x;
    int y;
};

void something(int *);

int main() {
    Point p{1, 2};

    something(&p.x);

    return p.y;
}

I expect, that main's return value can be optimized to return 2;, as something doesn't have access to p.y, it only gets a pointer to p.x.

But, none of the major compilers optimize the return value of main to 2. Godbolt.

Is there something in the standard, which allows something to modify p.y, if we only give access to p.x? If yes, does this depend on whether Point has standard layout?

What if I use something(&p.y);, and return p.x; instead?

curiousguy
  • 8,038
  • 2
  • 40
  • 58
geza
  • 28,403
  • 6
  • 61
  • 135
  • 10
    I don't understand the downvote and the flag. This is perfectly reasonable question to me. – Zereges Sep 17 '19 at 17:13
  • 1
    https://stackoverflow.com/questions/50803202/where-in-the-c-standard-is-the-memory-layout-of-objects-documented "Implementation alignment requirements might cause two adjacent members not to be allocated immediately after each other" – alter_igel Sep 17 '19 at 17:13
  • 1
    @alterigel: this shouldn't matter, as we can use `offsetof`. But I'm not sure that we can actually get a pointer to `p.y` from `p.x`, with the new pointer semantic rules of C++17. `std::launder` cannot be used, as a pointer to `p.x` can only reach `p.x`. So even if I could move the pointer to the correct location, maybe that pointer won't point to the object of `p.y`. – geza Sep 17 '19 at 17:17
  • 2
    If you change it to `void something(int * x) { *(x+1) = 6; }` it optimizes the return value to a fixed `6`, so the compiler at least sees that as a valid option to do. – t.niese Sep 17 '19 at 17:20
  • 2
    @zereges, didn't downvote, but almost did. The question is very hard to parse. I believe the OP is asking: "why can't the compiler realize the something() function will not modify p.y ?" If the OP asked that directly, there'd be less downvotes. – Jeffrey Sep 17 '19 at 17:26
  • @geza On a flat addressing arch, when a ptr is just a number, you technically don't even need `std::launder` because ptr are trivial type and a correct bit pattern is enough! But f.ex. the C committee made up an anti ptr guessing rule in a DR, as allowing users to come up with correct bit patterns of ptr by any means was too lax obviously. – curiousguy Sep 18 '19 at 12:44

1 Answers1

54

This is perfectly well-defined:

void something(int *x) {
    reinterpret_cast<Point*>(x)->y = 42;
}

The Point object (p) and its x member are pointer-interconvertible, from [basic.compound]:

Two objects a and b are pointer-interconvertible if:

  • [...]
  • one is a standard-layout class object and the other is the first non-static data member of that object, or, if the object has no non-static data members, any base class subobject of that object ([class.mem]), or:
  • [...]

If two objects are pointer-interconvertible, then they have the same address, and it is possible to obtain a pointer to one from a pointer to the other via a reinterpret_­cast.

That reinterpret_cast<Point*>(x) is valid and does end up with a pointer that points to p. Hence, modifying it directly is fine. As you can see, the standard-layout part and the first non-static data member part are significant.


Although it's not like the compilers in question optimize out the extra load if you pass a pointer to p.y in and return p.x instead.

Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    Thanks, I knew that I left a hole in my question. So, if I used `something(&p.y); return p.x;`, then it is clearly a missed optimization? – geza Sep 17 '19 at 17:24
  • @geza For some definition of "clearly". There almost certainly exists a lot of code that does things like this that such an optimization would break. – Barry Sep 17 '19 at 17:29
  • 1
    @geza Technically, yes, it would be a missed optimization then, but I don't think major compilers, at this time, are prepared to break all the existing code that uses `offsetof`. – Brian Bi Sep 17 '19 at 17:29
  • 2
    Doesn't that violate the strict-aliasing rule? Or is that a C-only rule? – Jazzwave06 Sep 17 '19 at 17:40
  • 2
    @sturcotte06 it does not because of *If two objects are pointer-interconvertible, then they have the same address, and it is possible to obtain a pointer to one from a pointer to the other via a reinterpret_­cast.* – NathanOliver Sep 17 '19 at 17:40
  • 3
    Am I missing something here? the cast changes the pointer to `p.x` into a pointer to `Point`, but nonetheless, it's still pointing _at_ the same memory location? That would mean this only works because `x` is the first member, and thus exists at the same memory location as the 'parent' object `Point p`? So if we were to pass `&p.y` into the function, we would get different behaviour (undefined?) – Baldrickk Sep 18 '19 at 09:06
  • Congrats for 200k! – lubgr Sep 18 '19 at 09:45