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

但是编译器抱怨

预期的常量表达式

大家都知道,除了编译器,表达式的值为 4,有什么问题吗?

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

4

3 回答 3

5

Addressing the immediate compiler error you are seeing in the supplied code, (Base2*)(Derive*)1 will most likely become reinterpret_casts when compiled, and that as DyP wrote as a comment to the question is not a constant expression which is required for enumeration initialization. Some compilers, notably GCC are not as strict on this point and will allow for reinterpret_cast in constant expressions even though it is forbidden by the standard (for further discussion of this see the comments for this GCC bug http://gcc.gnu.org/bugzilla/show_bug.cgi?id=49171 and Constexpr pointer value).

The broader question of identifying at compile time what the layout of an object is and the offsets to its various members is a tricky one without a well-defined answer. The standard give implementers a lot of latitude to pad/pack an object's fields, usually out of alignment considerations (for a good summary see http://www.altdevblogaday.com/2013/05/03/cc-low-level-curriculum-part-11-inheritance/). While the relative ordering of an object's field must be maintained, int y in an instance of Derived need not be at an offset of sizeof(x) from the start of the instance of Derived; the answer is compiler and target architecture dependent.

All of that being said, this sort of structure layout information is determined at compile time and at least on some compilers is made accessible (even if not in portable, standards compliant ways). In the answer to this question, C++ Compile-Time offsetof inside a template, Jesse Good provides some code that on GCC at least will allow one to determine field offsets within a type at compile time. This code will unfortunately not provide the correct offsets for base class members.

A good solution to your problem awaits implementation of compile time reflection support in C++, something for which there is ongoing work as part of a standards working group: https://groups.google.com/a/isocpp.org/forum/#!forum/reflection .

于 2014-01-02T02:50:44.120 回答
2

这是一个适用于 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

编译器资源管理器上提供的代码:https ://godbolt.org/z/Gfe6YK

基于来自constexpr 的有用注释和使用重新解释强制转换的静态 const void 指针的初始化,哪个编译器是正确的?,特别是包括指向相应 LLVM 提交的链接http://lists.llvm.org/pipermail/cfe-commits/Week-of-Mon-20120130/052477.html

于 2020-08-26T07:31:54.217 回答
0

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

于 2014-01-03T04:08:30.890 回答