我理解expr.add#4.2背后的推理,将
+
和-
限制为针对同一数组元素的指针,这在我关于偏移指针类的问题的几条评论中进行了解释。然而,通过指针和指针加偏移目标位于同一(嵌套)组合(不一定是数组)内也可以实现同样的效果。如果这些差异可能是任何东西,那么 offsetof 宏将毫无意义。
演示 offsetof 类比的示例:
#include <iostream>
#include <cstddef>
struct Inner
{
int i1;
float f;
bool b;
int i2;
};
struct Outer
{
int i1;
char c;
int i2;
Inner inner;
double d;
};
int main(int argc, char* argv[])
{
Outer outer;
std::cout << "reinterpret_cast<std::byte*>(&outer.i2) - reinterpret_cast<std::byte*>(&outer.i1): "
<< reinterpret_cast<std::byte*>(&outer.i2) - reinterpret_cast<std::byte*>(&outer.i1) << std::endl;
std::cout << "reinterpret_cast<std::byte*>(&outer.inner.i2) - reinterpret_cast<std::byte*>(&outer.i1): "
<< reinterpret_cast<std::byte*>(&outer.inner.i2) - reinterpret_cast<std::byte*>(&outer.i1) << std::endl;
std::cout << "offsetof(Outer, i2) - offsetof(Outer, i1): " << offsetof(Outer, i2) - offsetof(Outer, i1) << std::endl;
std::cout << "offsetof(Outer, inner) + offsetof(Inner, i2) - offsetof(Outer, i1): "
<< offsetof(Outer, inner) + offsetof(Inner, i2) - offsetof(Outer, i1) << std::endl;
return 0;
}
退货
reinterpret_cast<std::byte*>(&outer.i2) - reinterpret_cast<std::byte*>(&outer.i1): 8
reinterpret_cast<std::byte*>(&outer.inner.i2) - reinterpret_cast<std::byte*>(&outer.i1): 24
offsetof(Outer, i2) - offsetof(Outer, i1): 8
offsetof(Outer, inner) + offsetof(Inner, i2) - offsetof(Outer, i1): 24
所有涉及的指针目标都在对象内
outer
,但不是同一个对象。未定义的行为?
(在评论后编辑,非字节指针会因混合类型组合而中断,并且 expr.add#4.2 中的“假设”不满足我的需求。谢谢!)
支持定义行为的论据可以是:
offsetof
的语义要求所有组合成员在组合内都有固定的偏移量。是的,这段代码是未定义的行为。 我们来看看:
reinterpret_cast<std::byte*>(&outer.i2) - reinterpret_cast<std::byte*>(&outer.i1)
i1
是 outer
的第一个成员,因此可以与其进行指针互换。
这意味着通过 reinterpret_cast
,您可以获得指向 outer
的指针,以及指向表示 outer
对象的字节数组的指针。
但是,
i2
不是第一个成员,并且无法通过它访问该字节数组。
您还可以说 outer
中的所有存储字节都可以通过 i1
访问,但不能通过
i2
访问。 因此,根据[expr.add] p4,这个指针减法就是UB。 “可能假设的”部分也不相关,因为它仅适用于与任何数组的(假设的)后一位元素进行指针算术。