C++;铸造至底座和;覆盖";vptr问题 我刚刚读了一个新的C++挑战: 如果代码中有问题,对任何代码都有明显的影响,有些程序只适用于C++语言:-/P>

C++;铸造至底座和;覆盖";vptr问题 我刚刚读了一个新的C++挑战: 如果代码中有问题,对任何代码都有明显的影响,有些程序只适用于C++语言:-/P>,c++,windows,visual-c++,C++,Windows,Visual C++,注释中描述了一条特定的线(37)特别危险: ImageFactory::DebugPrintDimensions((ImageFactory::CustomImage*)image); 然后,该函数调用CustomImage(在CustomImage中首次定义)的虚拟方法 据称,这会导致CustomImage的第一个成员被视为实例的vptr(实际上是一个唯一的\u ptr),并使其指向的二进制文件被视为可执行(可能是恶意)代码 虽然我能理解这一点,但我想知道为什么这真的有效 CustomIma

注释中描述了一条特定的线(37)特别危险:

ImageFactory::DebugPrintDimensions((ImageFactory::CustomImage*)image);
然后,该函数调用
CustomImage
(在
CustomImage
中首次定义)的虚拟方法

据称,这会导致
CustomImage
的第一个成员被视为实例的vptr(实际上是一个
唯一的\u ptr
),并使其指向的二进制文件被视为可执行(可能是恶意)代码

虽然我能理解这一点,但我想知道为什么这真的有效

CustomImage
是一个虚拟类,因此(可能)它的前4个字节(假设X86)是vptr,而
unique_ptr
成员是下一个。。既然演员们似乎没有改变什么

。。。如何执行由
unique\u ptr
保存的数据?

我的看法(我非常高兴被纠正):

这里,
CustomImage
是一个多态类(在windowsabi下,vptr是第一个“成员”),但
Image
不是。定义的顺序意味着
ImageFactory
函数知道
CustomImage
Image
,但
main()
不知道

因此,当工厂这样做时:

Image* ImageFactory::LoadFromDisk(const char* imageName)
{
    return new (std::nothrow) CustomImage(imageName);
}
CustomImage*
指针被转换为
Image*
,一切正常。因为
Image
不是多态的,指针被调整为指向
CustomImage
中的(POD)
Image
实例——但基本上,这是在vptr之后,因为在MS ABI的多态类中,它总是排在第一位(我假设)

然而,当我们到达

ImageFactory::DebugPrintDimensions((ImageFactory::CustomImage*)image);
编译器会看到从一个它一无所知的类到另一个类的C样式转换。它所做的只是获取它拥有的地址,并假装它是一个
CustomImage*
。该地址实际上指向自定义映像中的
映像
;但由于
Image
是空的,并且假定空基类优化已生效,因此它最终指向
CustomImage
中的第一个成员,即
unique\u ptr

现在,
ImageFactory::DebugPrintDimensions()
假定它已被传递到一个指向完整的
CustomImage
的指针,因此该地址等于
vptr
的地址。但是它没有——它被交给了
unique\u ptr
的地址,因为在调用它的时候,编译器不知道什么更好。所以现在它取消了它认为是vptr(我们控制的真正数据)的引用,寻找虚拟函数的偏移量并盲目地执行它——现在我们遇到了麻烦

有几件事可以帮助缓解这种情况。首先,由于我们是通过基类指针操作派生类,
Image
应该有一个虚拟析构函数。这会使
Image
多态,而且很可能我们不会有问题(而且我们也不会泄漏内存)

其次,因为我们是从基转换到派生,所以应该使用
dynamic_cast
而不是C风格的转换,这将涉及运行时检查和正确的指针调整


最后,如果编译器在编译
main()。因此,也建议将类定义移动到
main()
上方。

可能内存布局是这样的,即vptr位于基本子对象之前,如下所示:

class CustomImage {
    void * __vptr;
    Image  __base;  // empty
    unique_ptr<whatever> evil;
};
class自定义图像{
无效*uu vptr;
图像_base;//为空
独特的邪恶;
};
这意味着从
Image*
CustomImage*
的有效转换需要从指针中减去几个字节。但是,您发布的邪恶类型设置在类定义之前,因此它不知道如何正确调整指针。相反,它的行为类似于
reinterpret\u cast
,只是假装指针指向
CustomImage
,而不调整其值

现在,由于基类是空的,因此
unique\u ptr
中的指针将被误解为vptr。这指向另一个指针,这将被误解为vtable指向第一个虚拟成员函数的指针。这又指向从文件加载的数据,当调用虚拟函数时,这些数据将作为代码执行。作为蛋糕上的糖衣,内存保护标志是从文件中加载的,并且不会进行调整以阻止执行

以下是一些经验教训:

  • 避免C样式转换,尤其是指针或引用类型。如果转换无效,他们会退回到重新解释施法,导致未定义行为的雷区。(更糟糕的是,它们的语法也是不可描述的,如果不仔细阅读代码,很容易漏掉。)
  • 避免非多态基类。它不仅在概念上存在疑问,而且会使删除变得更加笨拙和容易出错,它还会对内存布局产生令人惊讶的影响,正如我们在这里看到的那样。如果基类是多态的,我们可以使用
    dynamic_cast
    (或者通过提供合适的虚拟函数来避免强制转换),而不存在无效转换的可能性
  • 避免不必要的间接层次-无需将
    m_imageData
    作为指针
  • 切勿将用户数据放入可执行内存

您显然不会选择空基选项。考虑到了吗?@Parobay:是的,我考虑到了。如果未优化基础对象