【问题标题】:C++ experts: is the offset of a member variable to its class constant under these conditions?C++专家:在这些条件下,成员变量与其类常量的偏移量是多少?
【发布时间】:2008-12-19 22:55:40
【问题描述】:

给定一个FooClass* 类型的变量foo 和该类中名为bar 的成员变量,foo&(foo->bar) 之间的距离在任何情况下都相同,但有一些约束:

  1. FooClass 是非 POD 类型。

  2. 我们知道foo 将始终指向FooClass 的一个实例,而不是它的某个子类型。

  3. 我们只关心单个编译器和单个编译下的行为;也就是说,在 gcc 下这可能导致的值永远不会在使用 MSVC 编译的代码中使用,并且永远不会保存以在编译之间重新使用。用二进制计算,用二进制,就是这样。

  4. 我们不使用自定义 new,尽管该类的某些实例可能是堆栈分配的,而一些是堆分配的。

  5. FooClass 没有明确的ctor;它依赖于编译器生成的(FooClass 中的每个字段都是 POD 或默认可构造的)。

我无法在标准中找到任何一种方式的保证(我也没有预料到),但我对 gcc 的初步测试让我相信它总是会出现这种情况。我也知道这个保证是针对 POD 类型的,但是让我们假设这种类型不能是 POD。

更新/澄清:这仅适用于单个二进制文件的单个编译;计算出的偏移量永远不会离开那一次执行。基本上,我希望能够在静态映射中唯一标识一个类的字段,然后能够在该映射中查找一些宏/模板/EVIL 技巧。这只是我自己的娱乐,没有生命支持机器会依赖这个代码。

【问题讨论】:

    标签: c++


    【解决方案1】:

    编译程序后,是*

    偏移量将保持不变。

    但是有一个非常重要的限制:foo 必须专门指向 FooClass 对象。不是从 FooClass 派生的类,也不是其他任何东西。

    C++ 对 POD 成员偏移量进行区分的原因是,多重继承和 vtable 指针的位置(或缺少)都会造成对象地址与对象基地址不同的情况.

    【讨论】:

      【解决方案2】:

      在单个编译器下,编译器设置始终相同,FooClass 中没有添加或删除任何内容,那么是的,存储在foo&(foo->bar) 的地址之间的距离将始终相同,否则编译器将无法生成跨编译单元工作的正确代码。

      但是,一旦您向类中添加任何内容,或更改编译器设置,所有赌注都将失败。

      【讨论】:

        【解决方案3】:

        据我所知,无论是否是 POD 类,都应该如此。在编译时,根据编译器、体系结构、设置等,编译器确定类的大小及其所有成员的偏移量。然后为编译单元中类的所有实例修复此问题(如果保留单一定义规则,则扩展链接单元)。

        由于编译器按字面意思处理类型指针,即使底层类型错误(例如:指针被错误地转换为 c 样式),&foo 和 &(foo.bar) 之间的计算距离也是相同的,因为偏移量在编译时是静态已知的。

        注意:这肯定已经在以前有效地完成过。例如,请参阅 Microsoft 使用其“offsetof”宏的 ATL 数据绑定代码...

        【讨论】:

        • offsetoff 在标准中定义为仅采用 POD 类型/联合(第 18.1 节)
        • 该宏同样适用于非 POD 类型;我使用了类似的结构。它们对于为数据库访问机制映射数据绑定特别有用(与 MS 使用它的目的相同)。
        【解决方案4】:

        我不是专家,但无论如何我都会尝试回答你:)

        1. FooClass 是非 POD 类型。这意味着它可能有多个privatepublicprotected 的部分。在这样的部分中,顺序是成员定义的顺序,但在这些部分中,顺序是任意且未指定的。
        2. foo 将始终指向 FooClass。好吧,所以我们保证没有进行偏移调整。至少在一个编译中,偏移量将是相同的(没有备份的标准报价。但如果它们不同,它将无法工作)。
        3. 我们只关心单个编译器的行为。好吧,由于在访问修饰符的各个部分之间未指定成员的顺序,并且允许编译器在成员之间放置填充,这不会给我们带来太多好处。
        4. 我们只关心堆栈上的对象(自动存储持续时间)。好吧,我看不出这会如何改变对象布局。

        所以毕竟我不认为你有任何保证偏移量将在编译中保持不变。对于一个编译中的考虑(因此,如果我们使用一个编译器,其生成的代码使用随每次不同编译而变化的 ABI),偏移量就不能不同。但是,即使您知道偏移量,也无法访问该成员。访问成员的唯一方法是使用成员访问运算符 ->.(在 9.2/9 中已说明)。

        为什么不使用数据成员指针?它们允许安全地访问成员。这是一个示例:(looking up members by name)。

        【讨论】:

        • 我想唯一标识成员,以便我可以将它们放入一个“集合”中并对其进行迭代。它涉及模板和宏,所以它需要是自动的(我不能只在 ctor 中按名称来做)。我目前的黑客工作有效,但感谢您提供信息丰富的帖子!
        • 我正在做类似的事情(基于宏的 FooOptions 结构来构建需要许多参数来设置的 Foo 对象,一些是可选的,一些是必需的),我正在尝试尽可能少的击键- 如果我想输入更多内容,链接方式也可以。
        • @litb:有趣的是,我没有意识到编译器有义务按照成员在访问说明符部分中的声明顺序对其进行排序。
        • 顺便说一句,你专家 litb!
        【解决方案5】:

        有两件事会影响对象的内部布局:

        • 成员对象的大小。如果您更改成员定义,希望您能重新编译所有受影响的模块。
        • packing pragma 可能会更改在成员之间添加的填充。确保使用该类的所有模块的打包是相同的,或者至少是定义该类的部分。否则,您将遇到比不可预测的偏移更大的问题。

        【讨论】:

        • 如果成员变量是指针,那么成员对象的大小如何影响呢?当然,唯一重要的是您正在使用的编译器/平台的 ILP(整数、长整数、指针)模型。
        • 我说的是成员对象,而不是成员指针。如果您将“对象”的定义扩展为包括包括指针在内的 POD 类型,则断言仍然成立;只要指针大小(不是指向对象的大小)不变,就可以。
        【解决方案6】:

        底线:如果此类包含 POD 以外的任何内容,那么您绝对不能对偏移量做出任何假设。如果该类只是公共 POD 的集合,那么您是安全的。

        这里是一本优秀的中级 C++ 书中章节的一部分的链接。如果你对 C++ 很认真,我建议大家阅读这本书。

        这段特别的摘录解决了这里提出的问题的一部分:

        http://my.safaribooksonline.com/0321321928/ch11?portal=oreilly

        有关其他详细信息,请查看该书。我上面的“底线”是对本章的简单总结。

        【讨论】:

          【解决方案7】:

          是的,偏移量是在编译时确定的,所以只要您不比较编译器或编译器之间的偏移量,它就会始终保持不变。

          【讨论】:

            猜你喜欢
            • 2011-08-09
            • 1970-01-01
            • 2014-07-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-06-08
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多