为啥我在使用 memset 用零填充对象后在 dynamic_cast 出现异常

Posted

技术标签:

【中文标题】为啥我在使用 memset 用零填充对象后在 dynamic_cast 出现异常【英文标题】:Why am i getting an exception at dynamic_cast after filling object with zeros using memset为什么我在使用 memset 用零填充对象后在 dynamic_cast 出现异常 【发布时间】:2020-05-28 18:04:38 【问题描述】:

我在使用 dynamic_cast 时遇到了这个奇怪的运行时异常,但前提是我使用 memset 用零填充我正在投射的对象,或者只是使用 memcpy 将一些数据复制到其中。这是一个生成异常的示例。

class Base

public:
    virtual void func()  
;

class Derived : public Base

public: 
    void func() override      
;

int main()
   
    Derived derived;
    Base* base_ptr = &derived;
    memset(base_ptr, 0, sizeof(Derived));

    Derived* derived_ptr = dynamic_cast<Derived*>(base_ptr);    

并且异常信息是:

如果我使用 memcpy 将一个 Derived 对象复制到另一个对象,我会得到同样的异常。 任何人都知道发生了什么,它是否弄乱了dynamic_cast 使用的 RTTI?

我正在制作一个游戏引擎,它是一个实体组件系统,在程序的某一时刻,我从一个文件中加载所有对象及其组件。我实现不同组件的方式是使用继承(每个组件都派生自一个基础组件)。当我加载对象及其组件时,我不能只分配组件占用的内存量,我必须使用新的 组件名称,因为只有这样 dynamic_cast 才能工作。

我正在使用 Visual Studio 2019。

【问题讨论】:

您希望通过定义执行零初始化的默认构造函数或执行相同操作的方法无法实现的memset 实现什么? 我做了一个编辑,解释了我为什么使用 dynamic_cast 以及整个情况。 @Mark 我不明白这一点:“当我加载对象及其组件时,我不能只分配组件占用的内存量” 为什么不呢? “我必须使用新的组件名称,因为只有这样 dynamic_cast 才能工作”我不清楚这是什么意思。 如果我只是分配一些内存并使用 static_cast 将其转换为组件指针,那么 dynamic_cast 将不起作用,它不会知道它应该是哪个组件,是吗? 我不确定你需要什么,但听起来你想要placement new。请注意,这很棘手。您需要手动调用创建对象的析构函数。您不能将 memsetmemcpy 与具有 virtual 成员的类型一起使用。 【参考方案1】:

您的代码具有未定义的行为。 memset 要求它设置的对象是TriviallyCopyable,不是你的,因为它有虚函数。

这样做的结果是您正在覆盖编译器用于多态性的类中的数据,因此它无法解析转换,因为该信息现在是垃圾。

【讨论】:

感谢您的回复!知道如何解决这个问题吗?有简单的解决方案吗? @Mark 不要memset?在不知道你实际想要完成什么的情况下,这就是我能建议的全部内容。 @Mark 这仍然无助于您使用memset 的原因。理想情况下,您应该使用std::vector&lt;std::unique_ptr&lt;component&gt; 作为所有派生类的存储空间,然后您可以使用make_unique 而不是new 我没有使用 memset,我使用memcpy 将数据从一个对象复制到另一个对象,但我认为这个问题会更简单用memset解释。身份证 @mark, memcpy 有同样的要求。您不需要使用任何一个 using 指针【参考方案2】:

具有虚方法的类通常有一个用于 vtable 的 隐藏 指针成员。通过使用memset,您正在销毁该指针。

隐藏指针不是 C++ 语言规范所要求的,但它是迄今为止实现虚方法和 RTTI 最常用的方式。

【讨论】:

感谢您的回复!知道如何解决这个问题吗? @Mark 停止使用memset。它是 C 的保留,不打算用于对象。 @FrançoisAndrieux 确实如此,但是您知道编译器的工作方式与我描述的方式不同吗?有时对幕后发生的事情进行具体描述会使事情更容易理解。 “有时对幕后发生的事情进行具体描述会使事情更容易理解。”不幸的是,这经常导致误解,后来很难摆脱。

以上是关于为啥我在使用 memset 用零填充对象后在 dynamic_cast 出现异常的主要内容,如果未能解决你的问题,请参考以下文章

为啥没有用零填充

用零填充数字的嵌套 IF [重复]

用零填充整数(Java)[重复]

在现代 amd64 CPU 上进行 memset 的最快方法

用零填充的 varchar(5) 字段中的第一个未使用的数字

用零填充缺失的用户输入