选择the typical demonstration of how pointer values can change through casting:
struct B1 { int x; };
struct B2 { int y; };
struct D : B1, B2 { };
int main() {
D d;
cout << (B1*)&d << " " << (B2*)&d << " " << &d;
}
// Typical output:
// 0xbf814ab4 0xbf814ab8 0xbf814ab4
我开始思考;当B1
没有成员时,该偏移可能不存在,所以我检查了it's true(至少在这种情况下;不确定标准如何保证这种行为):
struct B1 { };
struct B2 { };
struct D : B1, B2 { };
int main() {
D d;
cout << (B1*)&d << " " << (B2*)&d << " " << &d;
}
// Typical output:
// 0xbf6ad95b 0xbf6ad95b 0xbf6ad95b
但是,sizeof(T)
不能是0
,所以sizeof(B1)
is still 1
。
令我感到震惊的是,这种“差异”可能会导致严重的容易出错的代码,程序员会认为(char*)(B2*)&d - (char*)(B1*)&d == (ptrdiff_t)sizeof(B1)
。
我的分析准确吗?
答案 0 :(得分:4)
B1
和B2
个对象是d
的子对象。 sizeof
运算符提供有关完整对象大小的信息,而不是子对象。
标准允许但不要求基类子对象不占用内存。因此,在另一个兼容的实现中,您可以在第二个示例中找到子对象毕竟具有不同的地址。
1.8p5:除非它是一个位字段,否则大多数派生对象的大小应为非零,并且应占用一个或多个字节的存储空间。基类子对象可以具有零大小。平凡可复制或标准布局类型的对象应占用连续的存储字节。
1.8p6:除非对象是零字段或零大小的基类子对象,否则该对象的地址是它占用的第一个字节的地址。两个不同的对象既不是位字段也不是零大小的基类子对象应具有不同的地址。
指针算法的唯一“安全”用途是:
y
的子对象x
的地址介于&x
和&x+1
之间。减去两个void*
指针是不正确的。您可能想要reinterpret_cast<char*>
或其他什么。 (代码风险很大的另一个迹象。)
5.7p4:出于这些运算符[binary
+
和-
]的目的,指向非阵列对象的指针与指向长度为1的数组的第一个元素的指针的行为相同对象的类型作为其元素类型。5.7p6:当减去指向同一数组元素的两个指针时,结果是两个数组元素的下标的差异。 ...除非两个指针都指向同一个数组对象的元素,否则行为是未定义的。