更具体地说,假设A
是B
的可访问基类,下面的代码是否会产生未定义的行为,并且断言是否根据标准禁止触发?
void test(B b1, B b2) {
A* a2 = &b2;
auto offset = reinterpret_cast<char*>(a2) - reinterpret_cast<char*>(&b2);
A* a1 = reinterpret_cast<A*>(reinterpret_cast<char*>(&b1) + offset);
assert(a1 == static_cast<A*>(&b1));
}
编辑:我知道所有常见的编译器供应商都以与test
的隐含假设兼容的方式实现C ++对象布局(即使在考虑虚拟继承时)。我正在寻找的是标准中此行为的保证(隐式或显式)。或者,也可以接受标准提供的对象存储布局保证范围的相当详细的描述,作为不保证这种行为的证据。
那可能没事。在某些特定条件下:
A
不是(virtual
基地的一部分),或者b1
和b2
具有相同的派生类型,或者您碰巧(非)幸运。
编辑:您从传递引用到传值的更改使得显示上述条件保持变得微不足道。
别名规则不会妨碍使用唯一错误的类型是char
,并且有一个明确的例外。
除非例如。在标准布局类型中,很难看出应该如何限制实现。例如,实现是否可以对基础对象使用某种动态查找?理论上,我猜,是的。 (同样,在实践中,我发现很难看到偏移的好处应该是静态的并且有额外的开销)
例如:
分配具有相同访问控制(第14条)的(非联合)类的非静态数据成员,以便后面的成员在类对象中具有更高的地址。具有不同访问控制的非静态数据成员的分配顺序未指定(第14条)。实施对齐要求可能导致两个相邻成员不能立即分配;因此,可能需要空间来管理虚拟功能(13.3)和虚拟基类(13.1)。
例如,该标准不保证虚拟基类的任何内容。
平凡可复制或标准布局类型(6.7)的对象应占用连续的存储字节。
同样,这仅适用于子集,因此标准在这里没有多大帮助。 (例如,具有虚函数的对象非常容易复制)。
另外,请参阅供应商实施的https://en.cppreference.com/w/cpp/types/offsetof宏偏移量
虽然仅对于成员变量,即使在这里,也很清楚,没有太多可继续的。
如您所见,大多数事情由实施决定。
也看到这个答案(不是同一个问题,但相关):C++ Standard On The Address of Inherited Members
不,由于与派生类或reinterpret_cast无关的原因:指针算术不能保证在数组上下文之外返回原始答案。请参阅5.7.4-5(expr.add),它指定何时添加/减去指针是有效的:
当向指针添加或从指针中减去具有整数类型的表达式时,结果具有指针操作数的类型。如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向偏离原始元素的元素,使得结果元素和原始数组元素的下标的差异等于整数表达式。 ...如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素,则评估不应产生溢出;否则,行为未定义。
减法的语言有点模棱两可,但基本上说同样的事情。