Why Can't You Use Offsetof on Non-Pod Structures in C++

Why can't you use offsetof on non-POD structures in C++?

Short answer: offsetof is a feature that is only in the C++ standard for legacy C compatibility. Therefore it is basically restricted to the stuff than can be done in C. C++ supports only what it must for C compatibility.

As offsetof is basically a hack (implemented as macro) that relies on the simple memory-model supporting C, it would take a lot of freedom away from C++ compiler implementors how to organize class instance layout.

The effect is that offsetof will often work (depending on source code and compiler used) in C++ even where not backed by the standard - except where it doesn't. So you should be very careful with offsetof usage in C++, especially since I do not know a single compiler that will generate a warning for non-POD use... Modern GCC and Clang will emit a warning if offsetof is used outside the standard (-Winvalid-offsetof).

Edit: As you asked for example, the following might clarify the problem:

#include <iostream>
using namespace std;

struct A { int a; };
struct B : public virtual A { int b; };
struct C : public virtual A { int c; };
struct D : public B, public C { int d; };

#define offset_d(i,f) (long(&(i)->f) - long(i))
#define offset_s(t,f) offset_d((t*)1000, f)

#define dyn(inst,field) {\
cout << "Dynamic offset of " #field " in " #inst ": "; \
cout << offset_d(&i##inst, field) << endl; }

#define stat(type,field) {\
cout << "Static offset of " #field " in " #type ": "; \
cout.flush(); \
cout << offset_s(type, field) << endl; }

int main() {
A iA; B iB; C iC; D iD;
dyn(A, a); dyn(B, a); dyn(C, a); dyn(D, a);
stat(A, a); stat(B, a); stat(C, a); stat(D, a);
return 0;
}

This will crash when trying to locate the field a inside type B statically, while it works when an instance is available. This is because of the virtual inheritance, where the location of the base class is stored into a lookup table.

While this is a contrived example, an implementation could use a lookup table also to find the public, protected and private sections of a class instance. Or make the lookup completely dynamic (use a hash table for fields), etc.

The standard just leaves all possibilities open by restricting offsetof to POD (IOW: no way to use a hash table for POD structs... :)

Just another note: I had to reimplement offsetof (here: offset_s) for this example as GCC actually errors out when I call offsetof for a field of a virtual base class.

Taking offset of a reference member (non PODs)

No. References are not objects, and they do not exist or have addresses or offsets. A pointer to a member reference is illegal.

Is this use of offsetof guaranteed to be correct?

The standard specifies behaviors of things, not how are they implemented. The implementation part is left to implementators - people who write compilers and libraries - and they have to implement things in a way they work in a way specified by the standard. If that implementation of standard library has by itself undefined behavior is irrelevant - it has to work with specified compiler, so specific compiler will interpret it in a way it has the behavior implementators want. Undefined behavior means that the standard specifies no requirement on the behavior of code. Your compiler documentation may specify additional requirements specifying behaviors of code that according to the standard are undefined - thus the code may be undefined by the standard and be perfectly fine and reasonable on a specific compiler that is required/written to interpret it in that way.

C++ language uses macros from C language when proper #include is used. The C standard says C99 7.17p3:

 The macros are [...] and

offsetof(type, member-designator)

which expands to an integer constant expression that has type size_t, the value of which is the offset in bytes, to the structure member (designated by member-designator), from the beginning of its structure (designated by type). The type and member designator shall be such that given

static type t;

then the expression &(t.member-designator) evaluates to an address constant. (If the specified member is a bit-field, the behavior is undefined.)

As a user of the language you do not care how it's implemented. If you use a standard compliant compiler, whatever it does behind the scenes, it should result in the behavior specified by the standard. Your usage of offsetof(PerObjBuffer, sampleType) is valid - having static PerObjBuffer t; then &(t.member-sampleType) evaluates to address constant - so offsetof(PerObjBuffer, sampleType) evaluates to integer constant expression. You like do not care how the compiler arrives at the result - it can use dark magic to do it - what matters it that compiler does it and the result represents the offset in bytes. (I think the famous example is memcpy - it's not possible to implement memcpy in standard compliant way, yet the function... exists).

Still, I fear that other parts of your code will be very invalid. The memcpy will most probably result in undefined behavior - you will copy padding between members and you seem to want to send that to some hardware location. In any case, I advise to do extensive research on lifetime and representation of C++ objects, on padding within structures, types of objects (ie. POD/standard layout/trivial) and how to work with them (ie. when it's ok to use mem* functions/placement new/std::launder).

Get offset to struct member without using offsetof

No, there's no simpler method than using offsetof, which was designed to do what you need.

Also, offsetof is not a function, but a macro, hiding the pointer arithmetic that you'd do if you didn't use offsetof.

So, clear answer: Since you don't even give a reason, it can't be an overly good reason. Get over it and use offsetof.



Related Topics



Leave a reply



Submit