是否必须静态定义C ++对象布局?

问题描述 投票:11回答:3

更具体地说,假设AB的可访问基类,下面的代码是否会产生未定义的行为,并且断言是否根据标准禁止触发?

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 ++对象布局(即使在考虑虚拟继承时)。我正在寻找的是标准中此行为的保证(隐式或显式)。或者,也可以接受标准提供的对象存储布局保证范围的相当详细的描述,作为不保证这种行为的证据。

c++ language-lawyer reinterpret-cast object-layout
3个回答
1
投票

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

A不是(virtual基地的一部分),或者b1b2具有相同的派生类型,或者您碰巧(非)幸运。

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

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


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


1
投票

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

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

减法的语言有点模棱两可,但基本上说同样的事情。

© www.soinside.com 2019 - 2024. All rights reserved.