为什么在c++中不能使用非pod结构的offsetof ?

时间:2021-08-06 21:10:46

I was researching how to get the memory offset of a member to a class in C++ and came across this on wikipedia:

我在研究如何将一个成员的内存偏移量用在c++的一个类上,并在*上发现了这个问题:

In C++ code, you can not use offsetof to access members of structures or classes that are not Plain Old Data Structures.

在c++代码中,您不能使用offsetof访问那些不是简单的旧数据结构的结构或类的成员。

I tried it out and it seems to work fine.

我试过了,看起来效果不错。

class Foo
{
private:
    int z;
    int func() {cout << "this is just filler" << endl; return 0;}

public: 
    int x;
    int y;
    Foo* f;

    bool returnTrue() { return false; }
};

int main()
{
    cout << offsetof(Foo, x)  << " " << offsetof(Foo, y) << " " << offsetof(Foo, f);
    return 0;
}

I got a few warnings, but it compiled and when run it gave reasonable output:

我得到了一些警告,但它编译并运行时给出了合理的输出:

Laptop:test alex$ ./test
4 8 12

I think I'm either misunderstanding what a POD data structure is or I'm missing some other piece of the puzzle. I don't see what the problem is.

我想我可能误解了一个POD数据结构或者我漏掉了另一个问题。我看不出有什么问题。

10 个解决方案

#1


32  

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.

简短的回答:offsetof是一个仅在遗留C兼容性的c++标准上的特性。因此,它基本上只局限于那些可以在C c++中完成的东西,而只支持它必须的C兼容性。

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.

由于offsetof基本上是一个依赖于简单的内存模型支持C的hack(实现为宏),它将从c++编译器的实现者那里获得大量的*来组织类实例布局。

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...

其效果是,即使在没有标准支持的情况下(取决于源代码和编译器的使用),在c++中也会经常工作。因此,在c++中,您应该非常小心,特别是因为我不知道一个编译器会对非pod使用发出警告……

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.

当试图在静态类型B中定位字段a时,它会崩溃,而当实例可用时,它就会起作用。这是因为虚拟继承,将基类的位置存储在一个查找表中。

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... :)

该标准只允许将所有可能性限制在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.

另一个注意:我必须重新实现这个例子的offsetof(这里:offset_s),因为当我调用一个虚拟基类的字段的offsetof时,GCC实际上是错误的。

#2


35  

Bluehorn's answer is correct, but for me it doesn't explain the reason for the problem in simplest terms. The way I understand it is as follows:

蓝角的回答是正确的,但对我来说,这并不能用最简单的术语解释问题的原因。我的理解是:

If NonPOD is a non-POD class, then when you do:

如果非pod是一个非pod类,那么当你这样做的时候:

NonPOD np;
np.field;

the compiler does not necessarily access the field by adding some offset to the base pointer and dereferencing. For a POD class, the C++ Standard constrains it to do that(or something equivalent), but for a non-POD class it does not. The compiler might instead read a pointer out of the object, add an offset to that value to give the storage location of the field, and then dereference. This is a common mechanism with virtual inheritance if the field is a member of a virtual base of NonPOD. But it is not restricted to that case. The compiler can do pretty much anything it likes. It could call a hidden compiler-generated virtual member function if it wants.

编译器不需要通过向基指针和取消引用添加一些偏移量来访问字段。对于一个POD类,c++标准限制它做这个(或其他类似的东西),但是对于一个非POD类,它没有。编译器可能会从对象中读取一个指针,然后向该值添加一个偏移量,以给出字段的存储位置,然后取消引用。如果字段是非pod的虚拟基础的成员,那么这是一个具有虚拟继承的公共机制。但它并不局限于这种情况。编译器可以做任何它喜欢的事情。如果它想要的话,它可以调用一个隐藏的编译器生成的虚拟成员函数。

In the complex cases, it is obviously not possible to represent the location of the field as an integer offset. So offsetof is not valid on non-POD classes.

在复杂的情况下,显然不可能将字段的位置表示为整数偏移量。所以offsetof在非pod类中是无效的。

In cases where your compiler just so happens to store the object in a simple way (such as single inheritance, and normally even non-virtual multiple inheritance, and normally fields defined right in the class that you're referencing the object by as opposed to in some base class), then it will just so happen to work. There are probably cases which just so happen to work on every single compiler there is. This doesn't make it valid.

在这种情况下,编译器就会将对象存储在一个简单的方法(如单继承,甚至通常非虚拟多重继承,类定义的字段,通常你引用的对象,而不是在某些基类),那么它就会工作。可能会有这样的情况发生在每个编译器上。这并不能使它有效。

Appendix: how does virtual inheritance work?

With simple inheritance, if B is derived from A, the usual implementation is that a pointer to B is just a pointer to A, with B's additional data stuck on the end:

对于简单的继承,如果从A派生出B,通常的实现是指向B的指针只是指向A的指针,而B的额外数据则被卡在末尾:

A* ---> field of A  <--- B*
        field of A
        field of B

With simple multiple inheritance, you generally assume that B's base classes (call 'em A1 and A2) are arranged in some order peculiar to B. But the same trick with the pointers can't work:

对于简单的多重继承,您通常假设B的基类(称为em A1和A2)是按照B的某些顺序排列的,但是指针的相同技巧是行不通的:

A1* ---> field of A1
         field of A1
A2* ---> field of A2
         field of A2

A1 and A2 "know" nothing about the fact that they're both base classes of B. So if you cast a B* to A1*, it has to point to the fields of A1, and if you cast it to A2* it has to point to the fields of A2. The pointer conversion operator applies an offset. So you might end up with this:

A1和A2“不知道”它们都是B的基类,所以如果你把B* * A1*,它必须指向A1的域,如果你把它投到A2*它必须指向A2的场。指针转换操作符应用一个偏移量。所以你可能会得出这样的结论:

A1* ---> field of A1 <---- B*
         field of A1
A2* ---> field of A2
         field of A2
         field of B
         field of B

Then casting a B* to A1* doesn't change the pointer value, but casting it to A2* adds sizeof(A1) bytes. This is the "other" reason why, in the absence of a virtual destructor, deleting B through a pointer to A2 goes wrong. It doesn't just fail to call the destructor of B and A1, it doesn't even free the right address.

然后,将B* * * * * * *转换为A1*不会更改指针值,但将其转换为A2*会增加sizeof(A1)字节。这是“其他”原因,在没有虚拟析构函数的情况下,通过指向A2的指针删除B是错误的。它不仅不能调用B和A1的析构函数,它甚至没有释放正确的地址。

Anyway, B "knows" where all its base classes are, they're always stored at the same offsets. So in this arrangement offsetof would still work. The standard doesn't require implementations to do multiple inheritance this way, but they often do (or something like it). So offsetof might work in this case on your implementation, but it is not guaranteed to.

无论如何,B“知道”它的所有基类在哪里,它们总是存储在相同的偏移量中。所以在这种安排下,还是可以工作的。这个标准不要求实现以这种方式进行多重继承,但他们经常这样做(或者类似的事情)。所以offsetof可能在这种情况下对您的实现起作用,但它不能保证。

Now, what about virtual inheritance? Suppose B1 and B2 both have A as a virtual base. This makes them single-inheritance classes, so you might think that the first trick will work again:

那么虚拟继承呢?假设B1和B2都有一个虚拟基地。这使它们成为单继承类,因此您可能认为第一个技巧将再次起作用:

A* ---> field of A   <--- B1* A* ---> field of A   <--- B2* 
        field of A                    field of A
        field of B1                   field of B2

But hang on. What happens when C derives (non-virtually, for simplicity) from both B1 and B2? C must only contain 1 copy of the fields of A. Those fields can't immediately precede the fields of B1, and also immediately precede the fields of B2. We're in trouble.

但是坚持下去。当C(非虚拟的,简单的)从B1和B2得到的时候会发生什么?C必须仅包含a字段的1个副本,这些字段不能在B1字段之前立即出现,而且在B2字段之前也不能立即出现。我们有麻烦了。

So what implementations might do instead is:

因此,实现的方法是:

// an instance of B1 looks like this, and B2 similar
A* --->  field of A
         field of A
B1* ---> pointer to A 
         field of B1

Although I've indicated B1* pointing to the first part of the object after the A subobject, I suspect (without bothering to check) the actual address won't be there, it'll be the start of A. It's just that unlike simple inheritance, the offsets between the actual address in the pointer, and the address I've indicated in the diagram, will never be used unless the compiler is certain of the dynamic type of the object. Instead, it will always go through the meta-information to reach A correctly. So my diagrams will point there, since that offset will always be applied for the uses we're interested in.

尽管我表明B1 *指向的第一部分对象的子对象后,我怀疑(还没来得及检查)的实际地址不存在,它会答:这只是开始,与简单的继承,实际地址的指针之间的偏移量,和地址我表示在图中,永远不会被使用,除非编译器的某些对象的动态类型。相反,它将始终通过元信息到达正确的。所以我的图会指向那里,因为这个偏移量总是被应用于我们感兴趣的用途。

The "pointer" to A could be a pointer or an offset, it doesn't really matter. In an instance of B1, created as a B1, it points to (char*)this - sizeof(A), and the same in an instance of B2. But if we create a C, it can look like this:

指向A的“指针”可以是指针或偏移量,这并不重要。在B1的实例中,创建为B1,它指向(char*)这个- sizeof(a),在B2的实例中相同。但如果我们创建一个C,它可以是这样的

A* --->  field of A
         field of A
B1* ---> pointer to A    // points to (char*)(this) - sizeof(A) as before
         field of B1
B2* ---> pointer to A    // points to (char*)(this) - sizeof(A) - sizeof(B1)
         field of B2
C* ----> pointer to A    // points to (char*)(this) - sizeof(A) - sizeof(B1) - sizeof(B2)
         field of C
         field of C

So to access a field of A using a pointer or reference to B2 requires more than just applying an offset. We must read the "pointer to A" field of B2, follow it, and only then apply an offset, because depending what class B2 is a base of, that pointer will have different values. There is no such thing as offsetof(B2,field of A): there can't be. offsetof will never work with virtual inheritance, on any implementation.

因此,使用指针或引用B2来访问一个字段需要的不仅仅是应用偏移量。我们必须读取“指向”B2字段的“指针”,然后执行它,然后只应用一个偏移量,因为根据b类的基数,该指针将具有不同的值。没有所谓的(b, A)的offsetof(A):不可能有。在任何实现中,offsetof都不会使用虚拟继承。

#3


4  

In general, when you ask "why is something undefined", the answer is "because the standard says so". Usually, the rational is along one or more reasons like:

一般来说,当你问“为什么有些东西没有定义”时,答案是“因为标准是这样说的”。通常,理性是沿着一个或多个原因,比如:

  • it is difficult to detect statically in which case you are.

    在这种情况下,很难用静态的方法来检测。

  • corner cases are difficult to define and nobody took the pain of defining special cases;

    角落的案例很难定义,也没有人能忍受定义特殊案例的痛苦;

  • its use is mostly covered by other features;

    它的使用主要包括其他功能;

  • existing practices at the time of standardization varied and breaking existing implementation and programs depending on them was deemed more harmful that standardization.

    在标准化的时候,现有的做法会改变并破坏现有的实现,而依赖于它们的程序被认为是更有害的标准化。

Back to offsetof, the second reason is probably a dominant one. If you look at C++0X, where the standard was previously using POD, it is now using "standard layout", "layout compatible", "POD" allowing more refined cases. And offsetof now needs "standard layout" classes, which are the cases where the committee didn't want to force a layout.

回到offsetof,第二个原因可能是主要原因。如果你看一下c++ 0X,这个标准以前使用POD,现在它使用的是“标准布局”,“布局兼容”,“POD”允许更精确的案例。现在,offsetof需要“标准布局”类,这是委员会不想强制布局的情况。

You have also to consider the common use of offsetof(), which is to get the value of a field when you have a void* pointer to the object. Multiple inheritance -- virtual or not -- is problematic for that use.

您还需要考虑使用offsetof()的常见用法,即当您的对象有一个void*指针时,它将获得一个字段的值。多重继承——不管是否是虚拟的——都有问题。

#4


1  

For the definition of POD data structure,here you go with the explanation [ already posted in another post in Stack Overflow ]

对于POD数据结构的定义,这里您可以使用说明(已经在Stack Overflow的另一个帖子中发布)

What are POD types in C++?

在c++中POD类型是什么?

Now, coming to your code, it is working fine as expected. This is because, you are trying to find the offsetof(), for the public members of your class, which is valid.

现在,来到您的代码,它是正常工作的。这是因为,您正在尝试为您的类的公共成员找到有效的offsetof()。

Please let me know, the correct question, if my viewpoint above, doesnot clarify your doubt.

请让我知道,正确的问题,如果我的观点,没有阐明你的怀疑。

#5


1  

I think your class fits the c++0x definition of a POD. g++ has implemented some of c++0x in their latest releases. I think that VS2008 also has some c++0x bits in it.

我认为您的类符合POD的c++0x定义。在最新版本中,g++已经实现了一些c++0x。我认为VS2008也有一些c++0x位。

From wikipedia's c++0x article

文章从*的c++ 0 x

C++0x will relax several rules with regard to the POD definition.

c++ 0x将放宽一些关于POD定义的规则。

A class/struct is considered a POD if it is trivial, standard-layout, and if all of its non-static members are PODs.

一个类/结构被认为是一个POD,如果它是微不足道的,标准布局,如果它的所有非静态成员都是POD。

A trivial class or struct is defined as one that:

一个微不足道的类或结构定义为:

  1. Has a trivial default constructor. This may use the default constructor syntax (SomeConstructor() = default;).
  2. 有一个简单的默认构造函数。这可能使用默认的构造函数语法(SomeConstructor() = default;)。
  3. Has a trivial copy constructor, which may use the default syntax.
  4. 有一个简单的复制构造函数,它可以使用默认的语法。
  5. Has a trivial copy assignment operator, which may use the default syntax.
  6. 有一个简单的复制赋值操作符,它可以使用默认的语法。
  7. Has a trivial destructor, which must not be virtual.
  8. 有一个平凡的析构函数,它不能是虚的。

A standard-layout class or struct is defined as one that:

标准布局类或结构定义为:

  1. Has only non-static data members that are of standard-layout type
  2. 只有非静态数据成员是标准布局类型吗?
  3. Has the same access control (public, private, protected) for all non-static members
  4. 对所有非静态成员具有相同的访问控制(公共、私有、保护)吗?
  5. Has no virtual functions
  6. 没有虚函数
  7. Has no virtual base classes
  8. 没有虚拟基类?
  9. Has only base classes that are of standard-layout type
  10. 是否只有标准布局类型的基类?
  11. Has no base classes of the same type as the first defined non-static member
  12. 没有与第一个定义的非静态成员相同类型的基类?
  13. Either has no base classes with non-static members, or has no non-static data members in the most derived class and at most one base class with non-static members. In essence, there may be only one class in this class's hierarchy that has non-static members.
  14. 要么没有具有非静态成员的基类,要么在大多数派生类中没有非静态数据成员,而且在大多数基类中都有非静态成员。在本质上,这个类的层次结构中可能只有一个类具有非静态成员。

#6


0  

If you add, for instance, a virtual empty destructor:

例如,如果您添加一个虚拟的空析构函数:

virtual ~Foo() {}

Your class will become "polymorphic", i.e. it will have a hidden member field which is a pointer to a "vtable" that contains pointers to virtual functions.

您的类将成为“多态”,即它将有一个隐藏的成员字段,它是指向“vtable”的指针,该“vtable”包含指向虚函数的指针。

Due to the hidden member field, the size of an object, and offset of members, will not be trivial. Thus, you should get trouble using offsetof.

由于隐藏的成员字段,对象的大小和成员的偏移量,将不会是微不足道的。因此,您应该使用offsetof来解决问题。

#7


0  

This seems to work fine for me:

这对我来说似乎没问题:

#define myOffset(Class,Member) ({Class o; (size_t)&(o.Member) - (size_t)&o;})

#8


0  

Works for me

适合我

   #define get_offset(type, member) ((size_t)(&((type*)(1))->member)-1)
   #define get_container(ptr, type, member) ((type *)((char *)(ptr) - get_offset(type, member)))

#9


-1  

I bet you compile this with VC++. Now try it with g++, and see how it works...

我打赌你是用vc++编译的。现在试试g++,看看效果如何……

Long story short, it's undefined, but some compilers may allow it. Others do not. In any case, it's non-portable.

长话短说,它是没有定义的,但是一些编译器可能允许它。别人不。无论如何,它是不可移植的。

#10


-1  

In C++ you can get the relative offset like this:

在c++中,你可以得到这样的相对偏移量:

class A {
public:
  int i;
};

class B : public A {
public:
  int i;
};

void test()
{
  printf("%p, %p\n", &A::i, &B::i); // edit: changed %x to %p
}

#1


32  

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.

简短的回答:offsetof是一个仅在遗留C兼容性的c++标准上的特性。因此,它基本上只局限于那些可以在C c++中完成的东西,而只支持它必须的C兼容性。

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.

由于offsetof基本上是一个依赖于简单的内存模型支持C的hack(实现为宏),它将从c++编译器的实现者那里获得大量的*来组织类实例布局。

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...

其效果是,即使在没有标准支持的情况下(取决于源代码和编译器的使用),在c++中也会经常工作。因此,在c++中,您应该非常小心,特别是因为我不知道一个编译器会对非pod使用发出警告……

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.

当试图在静态类型B中定位字段a时,它会崩溃,而当实例可用时,它就会起作用。这是因为虚拟继承,将基类的位置存储在一个查找表中。

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... :)

该标准只允许将所有可能性限制在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.

另一个注意:我必须重新实现这个例子的offsetof(这里:offset_s),因为当我调用一个虚拟基类的字段的offsetof时,GCC实际上是错误的。

#2


35  

Bluehorn's answer is correct, but for me it doesn't explain the reason for the problem in simplest terms. The way I understand it is as follows:

蓝角的回答是正确的,但对我来说,这并不能用最简单的术语解释问题的原因。我的理解是:

If NonPOD is a non-POD class, then when you do:

如果非pod是一个非pod类,那么当你这样做的时候:

NonPOD np;
np.field;

the compiler does not necessarily access the field by adding some offset to the base pointer and dereferencing. For a POD class, the C++ Standard constrains it to do that(or something equivalent), but for a non-POD class it does not. The compiler might instead read a pointer out of the object, add an offset to that value to give the storage location of the field, and then dereference. This is a common mechanism with virtual inheritance if the field is a member of a virtual base of NonPOD. But it is not restricted to that case. The compiler can do pretty much anything it likes. It could call a hidden compiler-generated virtual member function if it wants.

编译器不需要通过向基指针和取消引用添加一些偏移量来访问字段。对于一个POD类,c++标准限制它做这个(或其他类似的东西),但是对于一个非POD类,它没有。编译器可能会从对象中读取一个指针,然后向该值添加一个偏移量,以给出字段的存储位置,然后取消引用。如果字段是非pod的虚拟基础的成员,那么这是一个具有虚拟继承的公共机制。但它并不局限于这种情况。编译器可以做任何它喜欢的事情。如果它想要的话,它可以调用一个隐藏的编译器生成的虚拟成员函数。

In the complex cases, it is obviously not possible to represent the location of the field as an integer offset. So offsetof is not valid on non-POD classes.

在复杂的情况下,显然不可能将字段的位置表示为整数偏移量。所以offsetof在非pod类中是无效的。

In cases where your compiler just so happens to store the object in a simple way (such as single inheritance, and normally even non-virtual multiple inheritance, and normally fields defined right in the class that you're referencing the object by as opposed to in some base class), then it will just so happen to work. There are probably cases which just so happen to work on every single compiler there is. This doesn't make it valid.

在这种情况下,编译器就会将对象存储在一个简单的方法(如单继承,甚至通常非虚拟多重继承,类定义的字段,通常你引用的对象,而不是在某些基类),那么它就会工作。可能会有这样的情况发生在每个编译器上。这并不能使它有效。

Appendix: how does virtual inheritance work?

With simple inheritance, if B is derived from A, the usual implementation is that a pointer to B is just a pointer to A, with B's additional data stuck on the end:

对于简单的继承,如果从A派生出B,通常的实现是指向B的指针只是指向A的指针,而B的额外数据则被卡在末尾:

A* ---> field of A  <--- B*
        field of A
        field of B

With simple multiple inheritance, you generally assume that B's base classes (call 'em A1 and A2) are arranged in some order peculiar to B. But the same trick with the pointers can't work:

对于简单的多重继承,您通常假设B的基类(称为em A1和A2)是按照B的某些顺序排列的,但是指针的相同技巧是行不通的:

A1* ---> field of A1
         field of A1
A2* ---> field of A2
         field of A2

A1 and A2 "know" nothing about the fact that they're both base classes of B. So if you cast a B* to A1*, it has to point to the fields of A1, and if you cast it to A2* it has to point to the fields of A2. The pointer conversion operator applies an offset. So you might end up with this:

A1和A2“不知道”它们都是B的基类,所以如果你把B* * A1*,它必须指向A1的域,如果你把它投到A2*它必须指向A2的场。指针转换操作符应用一个偏移量。所以你可能会得出这样的结论:

A1* ---> field of A1 <---- B*
         field of A1
A2* ---> field of A2
         field of A2
         field of B
         field of B

Then casting a B* to A1* doesn't change the pointer value, but casting it to A2* adds sizeof(A1) bytes. This is the "other" reason why, in the absence of a virtual destructor, deleting B through a pointer to A2 goes wrong. It doesn't just fail to call the destructor of B and A1, it doesn't even free the right address.

然后,将B* * * * * * *转换为A1*不会更改指针值,但将其转换为A2*会增加sizeof(A1)字节。这是“其他”原因,在没有虚拟析构函数的情况下,通过指向A2的指针删除B是错误的。它不仅不能调用B和A1的析构函数,它甚至没有释放正确的地址。

Anyway, B "knows" where all its base classes are, they're always stored at the same offsets. So in this arrangement offsetof would still work. The standard doesn't require implementations to do multiple inheritance this way, but they often do (or something like it). So offsetof might work in this case on your implementation, but it is not guaranteed to.

无论如何,B“知道”它的所有基类在哪里,它们总是存储在相同的偏移量中。所以在这种安排下,还是可以工作的。这个标准不要求实现以这种方式进行多重继承,但他们经常这样做(或者类似的事情)。所以offsetof可能在这种情况下对您的实现起作用,但它不能保证。

Now, what about virtual inheritance? Suppose B1 and B2 both have A as a virtual base. This makes them single-inheritance classes, so you might think that the first trick will work again:

那么虚拟继承呢?假设B1和B2都有一个虚拟基地。这使它们成为单继承类,因此您可能认为第一个技巧将再次起作用:

A* ---> field of A   <--- B1* A* ---> field of A   <--- B2* 
        field of A                    field of A
        field of B1                   field of B2

But hang on. What happens when C derives (non-virtually, for simplicity) from both B1 and B2? C must only contain 1 copy of the fields of A. Those fields can't immediately precede the fields of B1, and also immediately precede the fields of B2. We're in trouble.

但是坚持下去。当C(非虚拟的,简单的)从B1和B2得到的时候会发生什么?C必须仅包含a字段的1个副本,这些字段不能在B1字段之前立即出现,而且在B2字段之前也不能立即出现。我们有麻烦了。

So what implementations might do instead is:

因此,实现的方法是:

// an instance of B1 looks like this, and B2 similar
A* --->  field of A
         field of A
B1* ---> pointer to A 
         field of B1

Although I've indicated B1* pointing to the first part of the object after the A subobject, I suspect (without bothering to check) the actual address won't be there, it'll be the start of A. It's just that unlike simple inheritance, the offsets between the actual address in the pointer, and the address I've indicated in the diagram, will never be used unless the compiler is certain of the dynamic type of the object. Instead, it will always go through the meta-information to reach A correctly. So my diagrams will point there, since that offset will always be applied for the uses we're interested in.

尽管我表明B1 *指向的第一部分对象的子对象后,我怀疑(还没来得及检查)的实际地址不存在,它会答:这只是开始,与简单的继承,实际地址的指针之间的偏移量,和地址我表示在图中,永远不会被使用,除非编译器的某些对象的动态类型。相反,它将始终通过元信息到达正确的。所以我的图会指向那里,因为这个偏移量总是被应用于我们感兴趣的用途。

The "pointer" to A could be a pointer or an offset, it doesn't really matter. In an instance of B1, created as a B1, it points to (char*)this - sizeof(A), and the same in an instance of B2. But if we create a C, it can look like this:

指向A的“指针”可以是指针或偏移量,这并不重要。在B1的实例中,创建为B1,它指向(char*)这个- sizeof(a),在B2的实例中相同。但如果我们创建一个C,它可以是这样的

A* --->  field of A
         field of A
B1* ---> pointer to A    // points to (char*)(this) - sizeof(A) as before
         field of B1
B2* ---> pointer to A    // points to (char*)(this) - sizeof(A) - sizeof(B1)
         field of B2
C* ----> pointer to A    // points to (char*)(this) - sizeof(A) - sizeof(B1) - sizeof(B2)
         field of C
         field of C

So to access a field of A using a pointer or reference to B2 requires more than just applying an offset. We must read the "pointer to A" field of B2, follow it, and only then apply an offset, because depending what class B2 is a base of, that pointer will have different values. There is no such thing as offsetof(B2,field of A): there can't be. offsetof will never work with virtual inheritance, on any implementation.

因此,使用指针或引用B2来访问一个字段需要的不仅仅是应用偏移量。我们必须读取“指向”B2字段的“指针”,然后执行它,然后只应用一个偏移量,因为根据b类的基数,该指针将具有不同的值。没有所谓的(b, A)的offsetof(A):不可能有。在任何实现中,offsetof都不会使用虚拟继承。

#3


4  

In general, when you ask "why is something undefined", the answer is "because the standard says so". Usually, the rational is along one or more reasons like:

一般来说,当你问“为什么有些东西没有定义”时,答案是“因为标准是这样说的”。通常,理性是沿着一个或多个原因,比如:

  • it is difficult to detect statically in which case you are.

    在这种情况下,很难用静态的方法来检测。

  • corner cases are difficult to define and nobody took the pain of defining special cases;

    角落的案例很难定义,也没有人能忍受定义特殊案例的痛苦;

  • its use is mostly covered by other features;

    它的使用主要包括其他功能;

  • existing practices at the time of standardization varied and breaking existing implementation and programs depending on them was deemed more harmful that standardization.

    在标准化的时候,现有的做法会改变并破坏现有的实现,而依赖于它们的程序被认为是更有害的标准化。

Back to offsetof, the second reason is probably a dominant one. If you look at C++0X, where the standard was previously using POD, it is now using "standard layout", "layout compatible", "POD" allowing more refined cases. And offsetof now needs "standard layout" classes, which are the cases where the committee didn't want to force a layout.

回到offsetof,第二个原因可能是主要原因。如果你看一下c++ 0X,这个标准以前使用POD,现在它使用的是“标准布局”,“布局兼容”,“POD”允许更精确的案例。现在,offsetof需要“标准布局”类,这是委员会不想强制布局的情况。

You have also to consider the common use of offsetof(), which is to get the value of a field when you have a void* pointer to the object. Multiple inheritance -- virtual or not -- is problematic for that use.

您还需要考虑使用offsetof()的常见用法,即当您的对象有一个void*指针时,它将获得一个字段的值。多重继承——不管是否是虚拟的——都有问题。

#4


1  

For the definition of POD data structure,here you go with the explanation [ already posted in another post in Stack Overflow ]

对于POD数据结构的定义,这里您可以使用说明(已经在Stack Overflow的另一个帖子中发布)

What are POD types in C++?

在c++中POD类型是什么?

Now, coming to your code, it is working fine as expected. This is because, you are trying to find the offsetof(), for the public members of your class, which is valid.

现在,来到您的代码,它是正常工作的。这是因为,您正在尝试为您的类的公共成员找到有效的offsetof()。

Please let me know, the correct question, if my viewpoint above, doesnot clarify your doubt.

请让我知道,正确的问题,如果我的观点,没有阐明你的怀疑。

#5


1  

I think your class fits the c++0x definition of a POD. g++ has implemented some of c++0x in their latest releases. I think that VS2008 also has some c++0x bits in it.

我认为您的类符合POD的c++0x定义。在最新版本中,g++已经实现了一些c++0x。我认为VS2008也有一些c++0x位。

From wikipedia's c++0x article

文章从*的c++ 0 x

C++0x will relax several rules with regard to the POD definition.

c++ 0x将放宽一些关于POD定义的规则。

A class/struct is considered a POD if it is trivial, standard-layout, and if all of its non-static members are PODs.

一个类/结构被认为是一个POD,如果它是微不足道的,标准布局,如果它的所有非静态成员都是POD。

A trivial class or struct is defined as one that:

一个微不足道的类或结构定义为:

  1. Has a trivial default constructor. This may use the default constructor syntax (SomeConstructor() = default;).
  2. 有一个简单的默认构造函数。这可能使用默认的构造函数语法(SomeConstructor() = default;)。
  3. Has a trivial copy constructor, which may use the default syntax.
  4. 有一个简单的复制构造函数,它可以使用默认的语法。
  5. Has a trivial copy assignment operator, which may use the default syntax.
  6. 有一个简单的复制赋值操作符,它可以使用默认的语法。
  7. Has a trivial destructor, which must not be virtual.
  8. 有一个平凡的析构函数,它不能是虚的。

A standard-layout class or struct is defined as one that:

标准布局类或结构定义为:

  1. Has only non-static data members that are of standard-layout type
  2. 只有非静态数据成员是标准布局类型吗?
  3. Has the same access control (public, private, protected) for all non-static members
  4. 对所有非静态成员具有相同的访问控制(公共、私有、保护)吗?
  5. Has no virtual functions
  6. 没有虚函数
  7. Has no virtual base classes
  8. 没有虚拟基类?
  9. Has only base classes that are of standard-layout type
  10. 是否只有标准布局类型的基类?
  11. Has no base classes of the same type as the first defined non-static member
  12. 没有与第一个定义的非静态成员相同类型的基类?
  13. Either has no base classes with non-static members, or has no non-static data members in the most derived class and at most one base class with non-static members. In essence, there may be only one class in this class's hierarchy that has non-static members.
  14. 要么没有具有非静态成员的基类,要么在大多数派生类中没有非静态数据成员,而且在大多数基类中都有非静态成员。在本质上,这个类的层次结构中可能只有一个类具有非静态成员。

#6


0  

If you add, for instance, a virtual empty destructor:

例如,如果您添加一个虚拟的空析构函数:

virtual ~Foo() {}

Your class will become "polymorphic", i.e. it will have a hidden member field which is a pointer to a "vtable" that contains pointers to virtual functions.

您的类将成为“多态”,即它将有一个隐藏的成员字段,它是指向“vtable”的指针,该“vtable”包含指向虚函数的指针。

Due to the hidden member field, the size of an object, and offset of members, will not be trivial. Thus, you should get trouble using offsetof.

由于隐藏的成员字段,对象的大小和成员的偏移量,将不会是微不足道的。因此,您应该使用offsetof来解决问题。

#7


0  

This seems to work fine for me:

这对我来说似乎没问题:

#define myOffset(Class,Member) ({Class o; (size_t)&(o.Member) - (size_t)&o;})

#8


0  

Works for me

适合我

   #define get_offset(type, member) ((size_t)(&((type*)(1))->member)-1)
   #define get_container(ptr, type, member) ((type *)((char *)(ptr) - get_offset(type, member)))

#9


-1  

I bet you compile this with VC++. Now try it with g++, and see how it works...

我打赌你是用vc++编译的。现在试试g++,看看效果如何……

Long story short, it's undefined, but some compilers may allow it. Others do not. In any case, it's non-portable.

长话短说,它是没有定义的,但是一些编译器可能允许它。别人不。无论如何,它是不可移植的。

#10


-1  

In C++ you can get the relative offset like this:

在c++中,你可以得到这样的相对偏移量:

class A {
public:
  int i;
};

class B : public A {
public:
  int i;
};

void test()
{
  printf("%p, %p\n", &A::i, &B::i); // edit: changed %x to %p
}