当从此到子类中的新对象时,将显示警告"destination for this 'memcpy' call is a pointer to dynamic class..."

When memcpy from this to a new object in a child class, warning "destination for this 'memcpy' call is a pointer to dynamic class..." shows

本文关键字:for destination memcpy this call dynamic class to pointer is 警告      更新时间:2023-10-16

我将使用虚拟复制函数创建一些父类和子类,该函数返回self的副本:

class A{
public:
int ID;
virtual A* copy(){
return new A();
}
}
class B : public A{
public:
int subID;
virtual A* copy(){
B* b=new B();
memcpy(b,this,sizeof(B));
return b;
}
};

编译时,它会显示以下警告:

destination for this 'memcpy' call is a pointer to dynamic class 'B' ; vtable pointer will be overwritten
explicitly cast the pointer to silence this warning

此警告是什么意思,它会导致哪些潜在问题?

这意味着这将不起作用。C++对象不应使用 C 库的memcpy() 函数进行复制(某些有限情况除外),该函数对 C++ 类、它们的构造函数、析构函数、虚拟方法以及 C++ 中没有的 C 中的所有其他内容一无所知。

你想要的是一个复制构造函数。它的工作正是您要完成的:制作现有对象的副本。

virtual A* copy(){
B* b=new B(*this);
return b;
}

这是警告,而不是错误。这是有原因的:这取决于你实际用什么精确地memcpy()它是否会导致动态对象的任何不当行为。

所以是的,通过赋值/构造函数复制比使用虚拟方法在C++对象上进行原始内存复制更安全(因此在怀疑时使用它),但是对它们使用memcpy()不一定总是错误。如果你正确地使用memcpy(),你就不会得到不当行为。

详细地:

对于大多数编译器,动态C++对象会自动获得一个名为vpointer的附加成员,该成员通常由编译器作为对象的第一个成员生成,因此位于对象的内存位置的最开头。vpointer 只是一个指针,即类的vtable所在的内存地址。您可以轻松检查以下内容:

#include <stdlib.h>
#include <stdint.h>
// Non-dynamic class.
class Foo {
public:
int a;
void asdf() {}
};
// Dynamic class.
class Bar {
public:
int a;
virtual void asdf() {}
};
int main(int argc, char **argv) {
Foo foo;
Bar bar;
printf("&foo=%p &foo.a=%p delta=%lldn", &foo, &foo.a, uint64_t(&foo.a) - uint64_t(&foo));
printf("&bar=%p &bar.b=%p delta=%lldn", &bar, &bar.a, uint64_t(&bar.a) - uint64_t(&bar));
return 0;
}

运行此代码时,您将看到成员变量aclass Foo与对象本身具有完全相同的内存地址。而class Bar的成员变量a具有带有偏移量的内存地址(例如 +8)。这是因为编译器将vpointer作为第一个成员变量注入到class Bar的对象(作为隐藏成员变量)。

(只读)vtable本身(对象的 vpointer 指向的位置)由特定动态类的所有对象在每个类级别共享,并且vtable用于将虚拟方法名称转换为该虚拟方法的一个特定实现。

因此,当您memcpy()动态对象时,您必须仔细处理对象包含 vpointer 的事实。只需将动态对象数组从一个内存位置复制到另一个内存位置,同时真正复制整个对象并保留其类型,通常是安全的。一个常见的用例是有效地增加包含动态对象的容器的大小,方法是将它们memcpy()到具有更大尺寸的新malloc()内存块,然后free()旧块。

另一方面,一个有问题的示例是当您尝试将一个动态类中的对象memcpy()到另一个动态类的对象时。在这种情况下,您至少会覆盖 vpointer 并更改类类型,因此可以在memcpy()之前和之后执行不同的方法。但再说一遍:这取决于您的用例是否是"不当行为"。您甚至可能打算更改类类型(包括对象到虚拟方法的映射)。

另外:从上面的代码示例中可以看出,当您从动态类的对象memcpy()到非动态类的对象,反之亦然时,您必须意识到由于注入的 vpointer 而导致的内存布局差异。特别是在这个例子中,vpointer 的存在与不存在是很棘手的。我的意思是,偶尔强制使用 C 样式的指针转换是很常见的,因此可能无法满足您的想法。

另一个问题是兼容性:官方C++并没有规定编译器应该如何实现动态对象。因此,即使我在这里写的东西基本上适用于所有主要的编译器,正式C++甚至不需要vtable或vpointer的存在。因此,从理论上讲,编译器可以自由地以完全不同的方式实现动态对象,这可能会对整个问题进行不同的评估。但是这个理论问题可以通过为您的软件编写测试用例来轻松解决。

当一个类型声明虚拟成员时,这种类型的每个实例(对象)或从它继承的实例(对象)都必须以某种方式知道它真正的底层类型。因为如果您需要多态性,那么这可能意味着您的代码的某些部分不一定具有此信息来做出正确的决策(例如,知道何时调用重写的方法而不是基方法)。

据我所知,这总是通过在你的对象的内部存储一个神奇的自动生成的成员来实现的,但不一定是强制性的。这个成员可以做很多事情,从而解决所有与多态性相关的问题,但它作为一个你不应该修补的黑匣子。它甚至没有向你公开一个名字,甚至没有一个界面。这个特定于实现的魔法称为 vtable。

现在,如果你回到对象的最开头,也就是这个成员所在的位置,你将用垃圾不相关的数据践踏它的值。您将有效地破坏此对象自己的类型感知,并且很可能在使用垃圾vtable后立即崩溃。