编译时基类指针偏移到Derive类

时间:2013-09-28 08:58:19

标签: c++

class Base1 {
    int x;
};
class Base2 {
   int y;
};
class Derive : public Base1, public Base2 {
public:
    enum {
        PTR_OFFSET = ((int) (Base2*)(Derive*)1) - 1,
    };
};

但编译器抱怨

  

预期的常量表达式

每个人都知道表达式值4除了编译器,出了什么问题?

那么如何在编译时获得偏移?

3 个答案:

答案 0 :(得分:2)

解决您在提供的代码中看到的立即编译器错误,(Base2*)(Derive*)1很可能在编译时变为reinterpret_casts,并且DyP写为对问题的注释不是枚举所需的常量表达式初始化。有些编译器,特别是GCC在这一点上并不严格,并且允许在常量表达式中使用reinterpret_cast,即使它被标准禁止(有关此进一步讨论,请参阅此GCC错误的注释http://gcc.gnu.org/bugzilla/show_bug.cgi?id=49171和{{3 }})。

在编译时识别对象的布局是什么以及对其各个成员的偏移这一更广泛的问题是一个棘手的问题,没有明确的答案。该标准为实施者提供了很大的自由来填充/打包对象的字段,通常不符合对齐考虑(有关摘要的详细信息,请参阅Constexpr pointer value)。虽然必须维护对象字段的相对顺序,但int y实例中的Derived不能与sizeof(x)实例的开头偏离Derived ;答案是依赖于编译器和目标体系结构。

所有这些都说,这种结构布局信息是在编译时确定的,至少在一些编译器上是可访问的(即使不是以便携式,符合标准的方式)。在这个问题的答案中,http://www.altdevblogaday.com/2013/05/03/cc-low-level-curriculum-part-11-inheritance/,Jesse Good提供了一些代码,在GCC上至少可以允许人们在编译时确定类型中的字段偏移量。遗憾的是,此代码不能为基类成员提供正确的偏移量。

您的问题的一个很好的解决方案等待在C ++中实现编译时反射支持,作为标准工作组的一部分,正在进行工作:C++ Compile-Time offsetof inside a template

答案 1 :(得分:0)

最近,我发现代码((int) (Base2*)(Derive*)1) - 1Derive : public virtual Base2时崩溃了。也就是说,虚拟基数的偏移量在编译时是未知的。因此,它在c ++标准中被禁止。

答案 2 :(得分:0)

以下是适用于Clang的示例。

该方法使用Clang和GCC均可用的内置函数。我已经验证了Clang(请参见下面的“代码资源管理器”链接),但尚未尝试使用GCC。

#include <iostream>

/* Byte offsets are numbered here without accounting for padding (will not be correct). */
struct A { uint64_t byte_0, byte_8; uint32_t byte_16; };
struct B { uint16_t byte_20, byte_24; uint8_t byte28; uint64_t byte_29; };
struct C { uint32_t byte_37; uint8_t byte_41; };
struct D { uint64_t byte_42; };
struct E : A, B, C, D {};

template<typename Type, typename Base> constexpr const uintmax_t
offsetByStaticCast() {
  constexpr const Type* Type_this = __builtin_constant_p( reinterpret_cast<const Type*>(0x1) )
                                  ? reinterpret_cast<const Type*>(0x1)
                                  : reinterpret_cast<const Type*>(0x1);

  constexpr const Base* Base_this = static_cast<const Base*>( Type_this );

  constexpr const uint8_t* Type_this_bytes = __builtin_constant_p( reinterpret_cast<const uint8_t*>(Type_this) )
                                           ? reinterpret_cast<const uint8_t*>(Type_this)
                                           : reinterpret_cast<const uint8_t*>(Type_this);

  constexpr const uint8_t* Base_this_bytes = __builtin_constant_p( reinterpret_cast<const uint8_t*>(Base_this) )
                                           ? reinterpret_cast<const uint8_t*>(Base_this)
                                           : reinterpret_cast<const uint8_t*>(Base_this);

  constexpr const uintmax_t Base_offset = Base_this_bytes - Type_this_bytes;
  
  return Base_offset;
}

int main()
{
  std::cout << "Size of A: " << sizeof(A) << std::endl;
  std::cout << "Size of B: " << sizeof(B) << std::endl;
  std::cout << "Size of C: " << sizeof(C) << std::endl;
  std::cout << "Size of D: " << sizeof(D) << std::endl;
  std::cout << "Size of E: " << sizeof(E) << std::endl;

  /* Actual byte offsets account for padding. */
  std::cout << "A offset via offsetByStaticCast<E, A>(): " << offsetByStaticCast<E, A>() << std::endl;
  std::cout << "B offset via offsetByStaticCast<E, B>(): " << offsetByStaticCast<E, B>() << std::endl;
  std::cout << "C offset via offsetByStaticCast<E, C>(): " << offsetByStaticCast<E, C>() << std::endl;
  std::cout << "D offset via offsetByStaticCast<E, D>(): " << offsetByStaticCast<E, D>() << std::endl;

  return 0;
}

输出:

Size of A: 24
Size of B: 16
Size of C: 8
Size of D: 8
Size of E: 56
A offset via offsetByStaticCast<E, A>(): 0
B offset via offsetByStaticCast<E, B>(): 24
C offset via offsetByStaticCast<E, C>(): 40
D offset via offsetByStaticCast<E, D>(): 48
Program ended with exit code: 0

可在Compiler Explorer上找到的代码:https://godbolt.org/z/Gfe6YK

基于constexpr and initialization of a static const void pointer with reinterpret cast, which compiler is right?的有用评论,特别是指向相应LLVM提交http://lists.llvm.org/pipermail/cfe-commits/Week-of-Mon-20120130/052477.html的链接