Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ experts: is the offset of a member variable to its class constant under these conditions?

Tags:

c++

Given a variable foo of type FooClass* and a member variable in that class named bar, is the distance between foo and &(foo->bar) the same in any situation with some constraints:

  1. FooClass is a non-POD type.

  2. We know that foo will always point to an instance of FooClass, and not some subtype of it.

  3. We only care about behaviour under a single compiler and a single compilation; that is, the value this may result in under gcc is never used in code compiled with MSVC, and it is never saved to be re-used between compilations. It is computed in the binary and used in the binary, and that is it.

  4. We don't use a custom new, although some instances of the class may be stack-allocated and some heap-allocated.

  5. There is no explicit ctor for FooClass; it relies upon the compiler-generated one (and each of the fields in FooClass is either POD or default-constructable).

I can't find a guarantee either way on this in the standard (nor did I expect to), but my rudimentary testing with gcc leads me to believe that it will always be the case there. I also know that this guarantee is made for POD-types, but let us assume this type can't be POD.

An update/clarification: this is just for a single compilation of a single binary; the calculated offsets will never leave that single execution. Basically, I want to be able to uniquely identify the fields of a class in a static map and then be able to lookup into that map for some macro/template/EVIL trickery. It is merely for my own amusement, and no life support machines will rely on this code.

like image 494
hazzen Avatar asked Dec 19 '08 22:12

hazzen


4 Answers

After you have compiled your program, Yes*.

The offset will remain constant.

There is one very important restriction, however: foo must be pointing specifically to a FooClass object. Not a class derived from FooClass, or anything else for that matter.

The reason that C++ makes the POD distinction regarding member offsets is because both multiple inheritance and the location (or lack of) a vtable pointer can create situations where the address of an object is not the same as the address of that object's base.

like image 82
Drew Dormann Avatar answered Nov 10 '22 01:11

Drew Dormann


Under a single compiler where the compiler settings are always the same and there is nothing added to or taken away from FooClass, then yes, the distance between the address stored at foo and &(foo->bar) will always be the same, or the compiler wouldn't be able to generate proper code that worked across compilation units.

However, once you add anything to the class, or change the compiler settings, all bets are off.

like image 42
Joel Avatar answered Nov 10 '22 00:11

Joel


As far as I know, this should always be the case, POD class or not. At compile time, based on the compiler, architecture, settings, etc., the compiler determines the size of the class and the offsets of all its members. This is then fixed for all instances of the class in the compilation unit (and by extension the linked unit, if the one-definition rule is preserved).

Since the compiler treats type pointers literally, even if the underlying type is wrong (eg: the pointer has been c-style cast incorrectly), the computed distance between &foo and &(foo.bar) will be the same, since the offset is known statically at compile time.

Note: This has definitely been done before, effectively. See, for example, Microsoft's ATL data binding code using their 'offsetof' macro...

like image 21
Nick Avatar answered Nov 10 '22 00:11

Nick


I'm no expert but i gonna try answering you anyway :)

  1. FooClass is a non-POD type. This mean it could have more than one sections of private, public or protected. Within such a section, the order is that of the definition of the members, but across those sections, order is arbitrary and unspecified.
  2. foo will always point to FooClass. Well so we have guarantee there is no offset adjustment done. At least in one compilation, offsets will be the same then (don't have the backing up Standard quote. But it can't work if they were different).
  3. We only care about behavior on a single compiler. Well since the order of members is unspecified across sections of access modifiers and the compiler is allowed to put padding between members, this won't buy us much.
  4. We only care about objects on the stack (automatic storage duration). Well i don't see how that changes anything of the object layout.

So after all i don't think you have any guarantee that the offset will be constant across compilations. For considerations within one compilation (so if we would play with a compiler whose generated code uses an ABI that changes with each different compilation), the offset just can't be different. But even if you know the offset, you can't access the member. Your only way to access a member is using the member access operator -> and . (that's said in 9.2/9).

Why not use data member pointers? They allow accessing members safely. Here is an example: (looking up members by name).

like image 20
Johannes Schaub - litb Avatar answered Nov 09 '22 23:11

Johannes Schaub - litb