11

更具体地说,假设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. 我正在寻找的是标准中这种行为的保证(隐式或显式)。或者,标准提供的对象存储布局保证范围的合理详细描述,作为不保证此行为的证据,也将被接受。

4

3 回答 3

1

那可能没问题。在某些特定条件下:

A不是(部分)virtual基础,或者b1具有b2相同的最衍生类型,或者您碰巧(不)幸运。

编辑:您从按引用传递到按值传递的更改使得显示上述条件成立变得微不足道。

别名规则不会妨碍使用的唯一错误类型是char,并且有一个明确的例外。

于 2018-08-25T14:10:25.800 回答
1

不,因为与派生类或 reinterpret_cast 无关的原因:指针算术不能保证在数组上下文之外给您返回原始答案。请参阅5.7.4-5 (expr.add),它指定何时有效添加/减去指针:

当具有整数类型的表达式被添加到指针或从指针中减去时,结果具有指针操作数的类型。如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向与原始元素偏移的元素,使得结果和原始数组元素的下标之差等于积分表达式。...如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则评估不应产生溢出;否则,行为是 undefined

减法的语言有点模棱两可,但本质上说的是同一件事。

于 2018-09-02T13:45:36.070 回答
1

除非例如。一个标准布局类型,很难看出在这个意义上应该如何限制实现。例如,实现可以对基础对象使用某种动态查找吗?理论上,我想,是的。(同样,在实践中我发现很难看出偏移量应该是静态的并且有额外的开销)

例如:

分配具有相同访问控制(第 14 条)的(非联合)类的非静态数据成员,以便后面的成员在类对象中具有更高的地址。未指定具有不同访问控制的非静态数据成员的分配顺序(第 14 条)。实现对齐要求可能会导致两个相邻的成员不会被立即分配;管理虚拟功能(13.3)和虚拟基类(13.1)的空间要求也是如此。

例如,该标准不保证对虚拟基类的任何东西。

普通可复制或标准布局类型(6.7)的对象应占用连续的存储字节。

同样,这仅适用于一个子集,因此该标准在这里没有多大帮助。(例如,具有虚函数的对象复制起来很重要)。

另外,请参阅供应商实施的宏 offsetof https://en.cppreference.com/w/cpp/types/offsetof

尽管仅针对成员变量,但即使在这里,也很清楚没有太多可做的事情。

如您所见,大多数事情都留给实现来决定。

另请参阅此答案(不是同一个问题,但相关):C++ Standard On The Address of Inherited Members

于 2018-08-30T11:05:47.627 回答