C++通过别名指针以静默方式将错误的类型分配给数组元素

C++ silently assigns a wrong type to an array element via an alias pointer

本文关键字:错误 类型 分配 数组元素 方式 别名 指针 静默 C++      更新时间:2023-10-16

这是有问题的简化代码:

#include <iostream>
using namespace std;
struct A {
virtual void foo() {
cout<<"A::foo()"<<endl;
}
};
struct B : A {
void foo() {
cout<<"B::foo()"<<endl;
}
};
struct C : A {
void foo() {
cout<<"C::foo()"<<endl;
}
};
int main() {
B b[3];
A* a = b;
C c;
a[1] = c;   //what's happening here??
a[1].foo(); //prints B::foo() when virtual, and A::foo() when non-virtual
}

我的问题与动态或静态多态性无关,而是与行a[1] = c;的奇怪分配有关,这似乎完全没有效果。如果a是数组b的别名,那么该赋值至少应该给出一个警告,在使用GCC-10编译时不存在警告。 谁能澄清一下编译器在这一行中做了什么?

来自 operator_arithmetic#Additive_operators (强调我的(

  • 在任何情况下,如果指向类型与数组元素类型不同,则不考虑 cv 限定条件,则在每个级别上,如果元素本身是指针,则指针算术的行为是未定义的。特别是,指针算术与指向基的指针算术(指向派生对象数组的元素(是未定义的

所以a[1](相当于*(a + 1)(是未定义的行为。

您可以对b进行指针算术:

A* b1 = &b[1]; // OK
*b1 = c; // OK, but object slicing

更好的是,引用而不是指针(不检查nullptr,不鼓励使用指针算法(:

A& b1 = b[1]; // OK
b1 = c; // OK, but object slicing

a[1] = c;

此行中有对象切片和未定义的行为。

a[1]的类型是A,因此类型的对象C切片为类型A的对象。

a[1].foo(); //prints B::foo() when virtual, and A::foo() when non-virtual

B::foo()打印是因为赋值运算符不会更改对象的类型,因此它不会复制指向vtable的指针。vptr将指向B::foo()

如果构造C类型的新对象来代替旧对象,则将更改vptr和函数C::foo()将被调用。

只是为了说明,不要在实际代码中这样做:示例

例:

#include <iostream>
using namespace std;
struct A {
virtual void foo() {
cout<<"A::foo()"<<endl;
}
};
struct B : A {
void foo() {
cout<<"B::foo()"<<endl;
}
};
struct C : A {
void foo() {
cout<<"C::foo()"<<endl;
}
};
int main() {
B b[3];
A* a = b;
C c;
// Everything below is undefined behavior and works in this example only because of struct size equality.
a[1] = c;   // object slicing and undefined behavior. vptr still points to vtable of B
a[1].foo(); // B::foo()
new(b) C;   // creating new object in memory ob b[0] with vptr to vtable of C
a[0].foo(); // C::foo()
}

没有警告,因为对象切片是有效的操作。它可以是有意为之的。

由于"a"是指向基类 A 的指针,因此无法使用索引概念为其赋值。变量"a"只能指向数组的开头,也可以指向单个变量的地址。

在上面的情况下,如果你想分配变量'c',那么你可以通过以下代码分配c的地址,这将打印C::foo((作为输出。

a = &c;   
a->foo();