在下面的代码片段中,发生了缓冲区溢出,即在执行
u->b = *b;
时。
class A {
public:
int x;
A() {
x = 5;
}
};
class B {
public:
int x;
float y;
B() {
x = 10;
y = 3.14;
}
};
union U {
A a;
B b;
};
U* foo(U* u) {
B* b = new B();
u->b = *b;
return u;
}
int main() {
A* a = new A();
U* u = (U*) a;
u = foo(u);
return u->b.y;
}
从我读到的here来看,演员表
(U*) a
应该有明确的定义。据我理解,它不应该是sizeof(U) != sizeof(A)
。
看起来
U
和 A
是指针可相互转换的,但我不确定这是否意味着强制转换已定义良好。
在这种情况下,我在遵循标准时遇到了困难,欢迎任何帮助!
U
类型的对象可以与其子对象U::a
进行指针互换。这可以用 std::is_pointer_interconvertible_with_class(&U::a)
进行测试。
(U*) a
是reinterpret_cast<U*>(a)
。这是 static_cast<U*>(static_cast<void*>(a))
,其中 [expr.static.cast]p14 说:
否则,如果原始指针值指向一个对象 a,并且存在一个与
T
类型类似且可与a指针相互转换的对象 b,则结果是指向 b 的指针.
该地址处可能不存在U
类型的对象,这意味着您不会获得指向
U
的指针,而只是指向U
类型的*a
类型的指针。随后像 U
一样访问它是未定义的行为(出于明显的原因,在
[basic.lval]p11中给出)。 但是,这有可能避免 UB。
operator new
调用可以返回比请求更多的字节(
[basic.stc.dynamic.allocation]p2、[new.delete.single]p3),并且
operator new
隐式创建对象([intro.object] ]p14)和
std::is_implicit_lifetime_v<U>
。该 operator new
调用 could分配足够的字节来存储
U
对象,在这种情况下,它将隐式创建 U
,从而使其格式良好。更标准的方法是这样的:
A* a = static_cast<A*>(operator new(sizeof(U)));
U* u = reinterpret_cast<U*>(a);
u = foo(u);
return u->b.y;
这明确避免了您担心的尺寸问题。