类型系统漏洞是指编译器没有捕获当一个类型被强制转换为另一个不兼容类型的情况。假设有两个简单类:
class A
{
char i;
};
class B : public A
{
char j;
};
为了简化,我们忽略像填充等内容,并假设类型为A
的对象占用1个字节,类型为B
的对象占用2个字节。
现在,当您有一个类型为A
或类型为B
的数组时,它们将如下所示:
A a[4]:
=================
| 0 | 1 | 2 | 3 |
|-------|-------|
| i | i | i | i |
=================
B b[4]:
=================================
| 0 | 1 | 2 | 3 |
|-------|-------|-------|-------|
| i | j | i | j | i | j | i | j |
=================================
现在想象一下,您有指向这些数组的指针,然后将一个指针转换为另一个指针,这显然会导致问题:
a cast to B[4]:
=================================
| 0 | 1 | 2 | 3 |
|-------|-------|-------|-------|
| i | j | i | j | x | x | x | x |
=================================
数组中的前两个对象将解释第二个和第四个
A
的
i
成员作为它们的
j
成员。第二个和第三个成员访问未分配的内存。
b cast to A[4]:
=================
| 0 | 1 | 2 | 3 |
|-------|-------|
| i | i | i | i | x | x | x | x |
=================
这里情况正好相反,所有4个对象现在交替地将2个实例的和j
解释为它们自己的成员。而且数组的一半丢失了。
现在想象一下删除这样一个转换后的数组。哪些析构函数会被调用?哪些内存将被释放?此时你已经深陷困境。
但是,还有更多。
假设你有三个类类似于这样:
class A
{
char i;
};
class B1 : public A
{
float j;
};
class B2 : public A
{
int k;
};
现在你需要创建一个B1
指针数组:
B1* b1[4];
如果你将该数组转换为一个指向A
类型指针的数组,你可能会认为,"嗯,这很好,对吧"?
A** a = <evil_cast_shenanigans>(b1);
我的意思是,您可以将每个成员安全地作为指向A
的指针进行访问:
char foo = a[0]->i
但是你还可以做这个:
a[0] = new B2{}; // Uh, oh.
这是一个有效的赋值,没有编译器会抱怨,但你不应该忘记我们实际上正在处理作为指向 B1
对象指针数组创建的数组。它的第一个成员现在指向一个 B2
对象,你现在可以访问它并将其视为 B1
,而编译器却不会有任何提示。
float bar = b1[0]->j
所以您又陷入麻烦了,如果首先不允许这种向上转换,编译器就无法警告您。
为什么std::unique_ptr API会禁止从派生类指针向基类指针的转换?
我希望上面的解释给出了很好的理由。
它如何禁止这些转换?
它只是不提供任何进行转换的API。shared_ptr API有像static_pointer_cast
这样的转换函数,而unique_ptr API没有。
unique_ptr
似乎明确使用SFINAE https://godbolt.org/z/LGfULn - luk32