更具体地说,假设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 ++对象布局(即使考虑到虚拟继承)。我正在寻找的是对标准中此行为的保证(隐式或显式)。另外,也可以接受对该标准提供的对象存储布局保证范围的合理详细描述,以证明不能保证这种行为。
答案 0 :(得分:1)
可能很好。在某些特定条件下:
A
不是virtual
基的一部分,或者b1
和b2
的派生类型相同,或者您碰巧是(un-)幸运的。
编辑:从引用传递到值传递的更改很容易显示上述条件成立。
由于使用的唯一错误类型为char
,因此别名规则不会受到影响,并且存在明确的例外情况。
答案 1 :(得分:1)
除非,例如作为标准布局类型,很难在这种意义上看到如何限制实现。例如,实现可以对基础对象使用某种动态查找吗?从理论上讲,我想是的。 (再次,在实践中,我发现很难看到偏移量的好处是静态的并具有额外的开销)
例如:
具有相同访问权限的(非联盟)类的非静态数据成员 控件(第14条)已分配,以便以后的成员拥有更高的权限 类对象中的地址。非静态分配顺序 未指定具有不同访问控制的数据成员(第14条)。 实施对齐要求可能会导致两个相邻成员 不得彼此立即分配;所以可能 虚拟功能管理空间要求(13.3)和 虚拟基类(13.1)。
例如,该标准对虚拟基类没有任何保证。
普通可复制或标准布局类型(6.7)的对象应 占用连续的存储字节。
同样,这仅适用于一个子集,因此该标准在这里没有太大帮助。 (例如,具有虚拟功能的对象很容易复制)。
此外,请参见供应商实现的https://en.cppreference.com/w/cpp/types/offsetof的宏偏移量
尽管仅用于成员变量,但即使在这里,也很明显没有太多事情要做。
如您所见,大多数事情都由实现决定。
也可以看到以下答案(不是相同的问题,而是相关的):C++ Standard On The Address of Inherited Members
答案 2 :(得分:1)
不,由于与派生类或reinterpret_cast无关的原因:不能保证指针算术能够将您的原始答案返回数组的上下文之外。请参阅5.7.4-5(expr.add),其中指定了何时添加/减去指针才有效:
当将具有整数类型的表达式添加到指针或从指针中减去时,结果具有该类型 指针操作数的值。如果指针操作数指向数组对象的元素,并且该数组为 足够大,结果指向与原始元素偏移的元素,使得 结果数组元素和原始数组元素的下标等于整数表达式。 ... 如果指针操作数和结果都指向同一数组对象的元素,或者指向过去 数组对象的最后一个元素,求值不应产生溢出;否则,行为是 未定义。
减法的语言有点含糊,但是说的基本上是同一件事。