Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is a C++ object layout necessarily statically defined?

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.

like image 430
SomeStrangeUser Avatar asked Aug 25 '18 14:08

SomeStrangeUser


3 Answers

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.

like image 182
Mohan Avatar answered Sep 21 '22 18:09

Mohan


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

like image 21
darune Avatar answered Sep 22 '22 18:09

darune


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.

like image 28
Deduplicator Avatar answered Sep 20 '22 18:09

Deduplicator