-->

为什么你不能在C ++非POD结构使用offsetof?为什么你不能在C ++非POD结构使用off

2019-06-14 12:45发布

我是研究如何让存储成员的偏移量在C ++类和整个这个来到维基百科:

在C ++代码,你不能offsetof,来不属于普通老式的数据结构的结构或级别的接入成员使用。

我尝试过了,它似乎很好地工作。

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;
}

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

Laptop:test alex$ ./test
4 8 12

我想我要么误解一个POD数据结构是什么,或者我丢失了一些其他的一块拼图。 我看不出有什么问题。

Answer 1:

简短的回答:offsetof是一个特点,就是只在原有的C兼容的C ++标准。 因此,它基本上仅限于东西比能够以C C ++做只支持它必须对C的兼容性。

由于offsetof基本上是依赖于简单的内存模型支持下的黑客攻击(如微距实现),它会占用大量的自由远离C ++编译器实现者如何组织类的实例布局。

其效果是,将offsetof经常工作(取决于源代码和编译器使用)在C ++中甚至不被支持的标准,其中 - 除非它没有。 所以,你应该非常小心,在C ++ offsetof的使用,尤其是因为我不知道一个单一的编译器将生成非POD使用警告......如果现代GCC和锵会发出警告offsetof使用标准外( -Winvalid-offsetof )。

编辑 :你问例如,下面可以解释这个问题:

#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;
}

试图找到现场时,这会崩溃a内部类B静态,而它的工作原理,当一个实例是可用的。 这是因为虚拟继承,其中所述基类的位置被存储到查找表中的。

虽然这是一个人为的例子,实现可使用查找表还找到一个类实例的公共,保护和私有的部分。 或使查找完全动态的(使用哈希表的字段),等等。

该标准只留下通过限制offsetof,来POD(IOW开放所有的可能性:没有办法使用POD结构哈希表... :)

只是另注:我不得不重新实现offsetof(此处offset_s)在这个例子中为GCC实际上是错误的,当我打电话offsetof为虚基类的字段。



Answer 2:

Bluehorn的答案是正确的,但对我来说这并不能解释为在最简单的术语问题的原因。 我的理解是如下:

如果NonPOD是一种非POD类,那么当你这样做:

NonPOD np;
np.field;

编译器不一定通过添加一些偏移,基指针和解除引用访问字段。 对于一个POD类,C ++标准限制了它这样做(或等价的东西),但对于非POD类没有。 编译器可能会代替读出指针的对象,加一个偏移量值给予该领域的存储位置,然后解引用。 这与虚拟继承的共同机制如果该字段是NonPOD的虚拟基的成员。 但它并不局限于这种情况。 编译器可以做几乎任何事情它喜欢。 这可以称之为一个隐藏的编译器生成的虚成员函数,如果它想。

在复杂的情况下,显然是不可能来表示字段的位置为整数偏移。 因此offsetof是不能在非POD类有效。

在你的编译器恰好将对象存储在一个简单的方法(如单继承,通常甚至非虚多重继承,并且通常域定义权在你的,而不是引用对象的类案件在一些基类),那么它只会发生这样的工作。 有可能这只是让发生在每一个编译器有工作的情况。 这并不能使它有效。

附录:如何虚拟继承工作?

通过简单的继承,当B从A派生,但通常情况是,一个指向B是只是一个指向,用B的卡在年底额外的数据:

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

通过简单的多重继承,你通常认为B的基类(将它们称作A1和A2)被布置以某种顺序特有B.但与指针同样的伎俩无法工作:

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

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

然后铸造B *到A1 *不改变指针值,但它投射到A2 *增加sizeof(A1)字节。 这是“其他”的原因,在没有虚析构函数,通过指针A2删除乙出错。 它不只是不叫B和A1的析构函数,它甚至不无正确的地址。

总之,B“知道”,其中所有的基类,他们总是存储在相同的偏移。 因此,在这种安排offsetof仍然会工作。 该标准不要求实现做多重继承这种方式,但他们经常做(或类似的东西)。 因此offsetof可能在这种情况下,你的执行工作,但它不能保证。

现在,有关虚拟继承? 假设B1和B2都具有A作为虚拟基。 这使得他们的单继承的类,所以你可能会认为第一招将再次工作:

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

但是坚持下去。 当C导出(非实际上,为简单起见)从B1和B2会发生什么? Ç必须只包含A的字段的1个拷贝的那些字段不能刚好先B1的领域,并也刚好先B2的字段。 我们就麻烦了。

那么实现可能做的却是:

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

虽然我已经指出B1 *指向子对象后,该对象的第一部分,我怀疑(不打扰检查)的实际地址不会在那里,这将是A的开始这只是不像简单的继承,在指针的实际地址,我已经在图中所指示的地址之间的偏移量,将永远不会被使用,除非编译器是一定的动态对象的类型的。 相反,它总是会经历的元信息发送到正确的到达。 所以我的图表将指向那里,因为总会被应用于我们感兴趣的使用该偏移。

“指针”,以A可以是指针或偏移,它其实并不重要。 在B1的一个实例,作为B1创建的,它指向(char*)this - 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

所以访问使用指针或引用一个B2 A的场,需要的不仅仅是应用偏移更多。 我们必须阅读“指针A” B2的领域,遵循它,然后才应用偏移,因为这取决于什么B2级是一个基础,这个指针会有不同的值。 有作为没有这样的事情offsetof(B2,field of A) :不可能有。 offsetof 绝不会与虚拟继承工作,对任何实现。



Answer 3:

一般情况下,当你问:“ 为什么是不确定的东西 ”,得到的回答是“ 因为标准是这样说的 。” 通常情况下,合理的是沿着像一个或多个原因:

  • 也很难在你这种情况下,静态检测。

  • 极端情况难以界定,没有人规定了特殊情况下的疼痛;

  • 其用途主要是由其他功能覆盖;

  • 在标准化多样的时间现行做法,打破现有的实现,并根据他们的方案被认为是更为有害的是标准化。

回到offsetof,第二个原因可能是占主导地位。 如果你看的C ++ 0x,其中标准以前使用POD,它现在使用的“标准格式”,“布局兼容”,“POD”,允许更加细化的情况。 而offsetof现在需要“标准布局”类,这是在委员会并不想强制布局的情况。

你必须还要考虑)共同offsetof的使用(,这是当你有一个void *指向对象得到一个字段的值。 多重继承 - 虚拟与否 - 是针对使用中存在问题。



Answer 4:

我想你的类适合的C ++ 0x一个POD的定义。 G ++已经实施了一些的C ++ 0x在其最新版本。 我认为VS2008也有一些C ++在它0X位。

从维基百科的C ++ 0x的文章

的C ++ 0x将放松一些规则,关于POD的定义。

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

甲琐碎类或结构被定义为一个:

  1. 有一个平凡的默认构造函数。 这可以使用默认的构造的语法(SomeConstructor()=默认)。
  2. 有一个平凡的拷贝构造函数,可以使用默认的语法。
  3. 有一个平凡的拷贝赋值运算符,可以使用默认的语法。
  4. 有一个平凡的析构函数,它不能是虚拟的。

一个标准的布局类或结构被定义为一个:

  1. 只有非静态数据成员均为标准布局类型
  2. 具有相同的访问控制(公共,私有,保护)的所有非静态成员
  3. 没有虚函数
  4. 没有虚基类
  5. 有一个是标准布局型的只有基类
  6. 具有相同类型作为第一定义非静态成员的无基类
  7. 或者具有与非静态成员没有基类,或具有在最派生类,并在与非静态成员最多有一个基类中没有非静态数据成员。 从本质上说,有可能是唯一一个在这个类的层次结构类,它具有非静态成员。


Answer 5:

对于POD数据结构的定义,在这里你去解释[已经张贴在另一篇文章中的堆栈溢出]

什么是POD类型在C ++?

现在,来到你的代码,它是如预期正常工作。 这是因为,你正在努力寻找的offsetof(),为你的类,它是有效的公众成员。

请让我知道,正确的问题,如果我的上述观点,亘古不变的澄清你的疑问。



Answer 6:

这个屡试不爽和C和C使用其最便携版+

#define offset_start(s) s
#define offset_end(e) e
#define relative_offset(obj, start, end) ((int64_t)&obj->offset_end(end)-(int64_t)&obj->offset_start(start))

struct Test {
     int a;
     double b;
     Test* c;
     long d;
 }


int main() {
    Test t;
    cout << "a " << relative_offset((&t), a, a) << endl;
    cout << "b " << relative_offset((&t), a, b) << endl;
    cout << "c " << relative_offset((&t), a, c) << endl;
    cout << "d " << relative_offset((&t), a, d) << endl;
    return 0;
}

上面的代码只是需要你持有某个对象的实例,无论是结构或类。 然后你需要传递一个指针引用的类或结构来访问其字段。 为了确保您得到正确的偏移从未设置“开始”字段设置为下“结束”字段中。 我们使用编译器来找出地址偏移量是在运行时的东西。

这可以让你不必担心与编译器填充数据等问题



Answer 7:

如果添加,例如,虚拟空析构函数:

virtual ~Foo() {}

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

由于隐藏部件领域中,对象的大小,和成员的偏移,就不会是微不足道的。 因此,你应该使用offsetof得到麻烦。



Answer 8:

对我的作品

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


Answer 9:

我敢打赌,你用VC ++编译此。 现在,随着G ++尝试一下,看看它是如何工作的?

长话短说,这是不确定的,但一些编译器可能允许它。 有的则没有。 在任何情况下,它是不可移植。



Answer 10:

在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
}


Answer 11:

这似乎为我工作的罚款:

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


文章来源: Why can't you use offsetof on non-POD structures in C++?
标签: c++ offsetof