11

More specifically, assuming A is an accessible base class of B, does the following code produce undefined behavior, and is the assertion guarenteed not to fire according to the standard?

void test(B b1, B b2) {
  A* a2 = &b2;
  auto offset = reinterpret_cast<char*>(a2) - reinterpret_cast<char*>(&b2);
  A* a1 = reinterpret_cast<A*>(reinterpret_cast<char*>(&b1) + offset);
  assert(a1 == static_cast<A*>(&b1));
}

Edit: I'm aware that all of the common compiler vendors implement C++ object layout (even when taking into account virtual inheritence) in a way that is compatible with the implicit assumptions of test. What I'm looking for is a guarantee (either implicit or explicit) for this behavior in the standard. Alternatively, a reasonably detailed description of the extent of object storage layout guarantees provided by the standard, as proof that this behavior is not guaranteed, will also be accepted.

SomeStrangeUser
  • 727
  • 11
  • 16
  • It might not work if virtual inheritance is involved. Besides, all those casts probably exhibit undefined behavior somewhere by violating strict aliasing rules, but I'm too lazy to chase that down. – Igor Tandetnik Aug 25 '18 at 14:08
  • If `A` is a virtual base - no. –  Aug 25 '18 at 14:10
  • I would be surprised if there were any guarantees provided by the standard about the above. So it may work but it falls under the "Unspecified Behavior" – Martin York Aug 25 '18 at 15:04
  • 1
    I think that's only true for standard-layout types. For other types, the standard doesn't impose any such requirements, so implementations are allowed to do "crazy" things. Not that I know any implementations for which that `assert` would fail (for any type, even with virtual inheritance). – geza Aug 25 '18 at 23:07
  • Missing tag `language-lawyer` – Basile Starynkevitch Aug 28 '18 at 07:48
  • The size of an object is a static property. – Raedwald Aug 28 '18 at 08:35

3 Answers3

1

That may be fine. Under some specific conditions:

A is not (part of) a virtual base, or b1 and b2 have the same most derived type, or you happen to be (un-)lucky.

Edit: Your change from pass-by-reference to pass-by-value makes it trivial to show the condition above holds.

The aliasing-rules won't get in the way as the only wrong type used is char, and there is an explicit exception for that.

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
  • Is that really guarenteed to be true regardless of whether `A` or `B` are standard-layout objects or anything like that? – SomeStrangeUser Aug 25 '18 at 14:19
  • 2
    `makes it trivial to show the condition above holds.` This statement is possible wrong. OP is asking for standard conformance. Please provide documentation for this claim. – darune Sep 01 '18 at 19:21
1

Unless eg. a standard-layout type, it is hard to see how an implementation should be restricted in this sense. Could an implementation use some kind of dynamic lookup for the base object for example ? in theory, i guess, yes. (Again, in practice i find it hard to see what the benefit should be of the offset be static and have extra overhead)

For example:

Non-static data members of a (non-union) class with the same access control (Clause 14) are allocated so that later members have higher addresses within a class object. The order of allocation of non-static data members with different access control is unspecified (Clause 14). Implementation alignment requirements might cause two adjacent members not to be allocated immediately after each other; so might requirements for space for managing virtual functions (13.3) and virtual base classes (13.1).

The standard doesn't guarentee anything towards virtual base classes for example.

An object of trivially copyable or standard-layout type (6.7) shall occupy contiguous bytes of storage.

Again, this only goes for a subset, so the standard doesn't help much here. (eg. an object with a virtual function is non-trivial to copy).

Also, see the vendor implemented macro offsetof https://en.cppreference.com/w/cpp/types/offsetof

Although for member variables only, even here, it makes it pretty clear there is not much to go on.

As you can see, most things is left to the implementation to decide.

Also see this answer(not same question, but related): C++ Standard On The Address of Inherited Members

darune
  • 10,480
  • 2
  • 24
  • 62
  • Why do objects of standard-layout type have the same layout? – xskxzr Aug 30 '18 at 12:31
  • They are intented to be used for communication with other programming languages. – darune Aug 30 '18 at 17:29
  • @xskxzr: we can infer this information from the definition of `offsetof`. At least, I haven't found anything else regarding this, which means that the standard doesn't describe standard-layout well, in my opinion. – geza Sep 01 '18 at 10:10
1

No, for a reason that has nothing to do with derived classes or reinterpret_cast: The pointer arithmetic isn't guaranteed to give you back the original answer outside the context of an array. See 5.7.4-5 (expr.add) which specify when it's valid to add/subtract pointers:

When an expression that has integral type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the pointer operand points to an element of an array object, and the array is large enough, the result points to an element offset from the original element such that the difference of the subscripts of the resulting and original array elements equals the integral expression. ... If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.

The language for subtract is a bit more ambiguous, but says essentially the same thing.

Mohan
  • 7,302
  • 5
  • 32
  • 55
  • True, but 3.9.4 indicates that: "The object representation of an object of type T is the sequence of N unsigned char objects taken up by the object of type T, where N equals sizeof(T)." Moreover, the definition for `aligned_storage` (see 20.10.7.6), seems to imply that these char objects are contiguous, i.e. forming an array: "The member typedef type shall be a POD type suitable for use as uninitialized storage for any object whose size is at most Len" – SomeStrangeUser Sep 02 '18 at 21:36
  • An array object is not the same as a contiguous sequence of char objects. The former is a high-level concept defined in 8.3.4 of the standard. – Mohan Sep 03 '18 at 09:35