当从这个 memcpy 到子 class 中的新对象时,警告 "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
我要创建一些具有虚拟复制功能的父子 类,returns 自己的副本:
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 库的 memcpy
() 函数复制 C++ 对象(在某些有限情况下除外),该函数对 C++ 类、它们的构造函数、析构函数、虚方法以及其中的所有其他内容一无所知C 中没有的 C++。
你要的是copy constructor。它的工作正是您要完成的工作:制作现有对象的副本。
virtual A* copy(){
B* b=new B(*this);
return b;
}
当一个类型声明虚拟成员时,该类型的每个实例(对象)或从它继承的实例(对象)都必须以某种方式知道其真正的基础类型。因为如果你需要多态性,那么它可能意味着你的部分代码不一定有做出正确决定的信息(例如知道何时调用重写方法而不是基础方法)。
据我所知,这始终是通过在对象的内部存储一个神奇的、自动生成的成员来实现的,但不一定是强制性的。这个成员可以做很多事情,从而解决所有与多态性相关的问题,但它是作为一个你不应该去修改的黑盒子来做的。它甚至不会以名称甚至界面的形式向您公开。这段特定于实现的魔法称为 vtable。
现在,如果您 memcpy 到对象的最开头,也就是该成员所在的位置,您将用无关的垃圾数据践踏它的值。您将有效地破坏此对象自身的类型感知,并且很可能在使用垃圾 vtable 时立即崩溃。
这是警告,不是错误。这是有原因的:这取决于你对 memcpy()
所做的 精确地 是否会导致 动态对象 的任何不当行为].
所以是,通过assignment/constructor复制比原始更安全使用虚拟方法在 C++ 对象上进行内存复制(因此有疑问时使用它),但是 no 对它们使用 memcpy()
不一定总是错误。如果您对它们正确使用 memcpy()
,您将不会出现不当行为。
详细:
使用 大多数 编译器,动态 C++ 对象会自动获得一个 附加成员,称为 vpointer ,它 通常 由编译器作为对象的第一个成员生成,因此在对象内存位置的最开始。 vpointer 只是一个 pointer,它是 class 的 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=%lld\n", &foo, &foo.a, uint64_t(&foo.a) - uint64_t(&foo));
printf("&bar=%p &bar.b=%p delta=%lld\n", &bar, &bar.a, uint64_t(&bar.a) - uint64_t(&bar));
return 0;
}
当您 运行 这段代码时,您会看到 class Foo
的成员变量 a
与对象本身具有完全相同的内存地址。而 class Bar
的成员变量 a
具有带偏移量的内存地址(例如 +8)。那是因为编译器将 vpointer 作为第一个成员变量注入到 class Bar
的对象(作为一种隐藏成员变量)。
(只读)vtable 本身(对象的 vpointer 指向的地方)在每个 [=82] 上共享=] 特定动态 class 的所有对象的级别,并且 vtable 用于将虚拟方法名称转换为该虚拟方法的一个特定实现。
所以当你 memcpy()
动态对象时,你必须小心处理对象包含 vpointer 的事实。只需将一组动态对象从一个内存位置复制到另一个内存位置,同时真正复制整个对象并保留它们的类型,通常 是安全的。一个常见的用例是有效地增加包含动态对象的容器的大小,方法是 memcpy()
-ing 到一个新的 malloc()
-ated 的更大尺寸的内存块,然后 free()
-ing旧块。
另一方面,一个有问题的例子是当您尝试 memcpy()
一个对象从一个动态 class 到另一个动态 class 的对象时。在这种情况下,您至少会覆盖 vpointer 并更改 class 类型,因此可能会在 memcpy()
之前和之后执行不同的方法。但又一次:这是否是 "misbehaviour" 取决于您的用例。您甚至可能打算更改 class 类型(包括对象到虚拟方法的映射)。
此外:从上面的代码示例中可以看出,当您 memcpy()
从动态对象 class 到非动态对象时,反之亦然,您必须了解由于注入的 vpointer 而导致的内存布局差异。特别是这个例子中 vpointer 的存在与不存在是棘手的。我的意思是,偶尔强制执行 C 风格类型的指针转换是很常见的,这可能会导致您无法按照您的想法进行转换。
另一个问题是兼容性:C++ 官方并未规定编译器应如何准确地实现动态对象。所以即使我在这里写的东西基本上适用于所有主要的编译器,正式的 C++ 甚至不需要 vtable 或 vpointers 的存在。因此从理论上讲,编译器可以自由地以完全不同的方式实现动态对象,这可能会对这个整体问题产生不同的评估。但是通过为您的软件编写测试用例可以很容易地解决这个理论问题。
我要创建一些具有虚拟复制功能的父子 类,returns 自己的副本:
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 库的 memcpy
() 函数复制 C++ 对象(在某些有限情况下除外),该函数对 C++ 类、它们的构造函数、析构函数、虚方法以及其中的所有其他内容一无所知C 中没有的 C++。
你要的是copy constructor。它的工作正是您要完成的工作:制作现有对象的副本。
virtual A* copy(){
B* b=new B(*this);
return b;
}
当一个类型声明虚拟成员时,该类型的每个实例(对象)或从它继承的实例(对象)都必须以某种方式知道其真正的基础类型。因为如果你需要多态性,那么它可能意味着你的部分代码不一定有做出正确决定的信息(例如知道何时调用重写方法而不是基础方法)。
据我所知,这始终是通过在对象的内部存储一个神奇的、自动生成的成员来实现的,但不一定是强制性的。这个成员可以做很多事情,从而解决所有与多态性相关的问题,但它是作为一个你不应该去修改的黑盒子来做的。它甚至不会以名称甚至界面的形式向您公开。这段特定于实现的魔法称为 vtable。
现在,如果您 memcpy 到对象的最开头,也就是该成员所在的位置,您将用无关的垃圾数据践踏它的值。您将有效地破坏此对象自身的类型感知,并且很可能在使用垃圾 vtable 时立即崩溃。
这是警告,不是错误。这是有原因的:这取决于你对 memcpy()
所做的 精确地 是否会导致 动态对象 的任何不当行为].
所以是,通过assignment/constructor复制比原始更安全使用虚拟方法在 C++ 对象上进行内存复制(因此有疑问时使用它),但是 no 对它们使用 memcpy()
不一定总是错误。如果您对它们正确使用 memcpy()
,您将不会出现不当行为。
详细:
使用 大多数 编译器,动态 C++ 对象会自动获得一个 附加成员,称为 vpointer ,它 通常 由编译器作为对象的第一个成员生成,因此在对象内存位置的最开始。 vpointer 只是一个 pointer,它是 class 的 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=%lld\n", &foo, &foo.a, uint64_t(&foo.a) - uint64_t(&foo));
printf("&bar=%p &bar.b=%p delta=%lld\n", &bar, &bar.a, uint64_t(&bar.a) - uint64_t(&bar));
return 0;
}
当您 运行 这段代码时,您会看到 class Foo
的成员变量 a
与对象本身具有完全相同的内存地址。而 class Bar
的成员变量 a
具有带偏移量的内存地址(例如 +8)。那是因为编译器将 vpointer 作为第一个成员变量注入到 class Bar
的对象(作为一种隐藏成员变量)。
(只读)vtable 本身(对象的 vpointer 指向的地方)在每个 [=82] 上共享=] 特定动态 class 的所有对象的级别,并且 vtable 用于将虚拟方法名称转换为该虚拟方法的一个特定实现。
所以当你 memcpy()
动态对象时,你必须小心处理对象包含 vpointer 的事实。只需将一组动态对象从一个内存位置复制到另一个内存位置,同时真正复制整个对象并保留它们的类型,通常 是安全的。一个常见的用例是有效地增加包含动态对象的容器的大小,方法是 memcpy()
-ing 到一个新的 malloc()
-ated 的更大尺寸的内存块,然后 free()
-ing旧块。
另一方面,一个有问题的例子是当您尝试 memcpy()
一个对象从一个动态 class 到另一个动态 class 的对象时。在这种情况下,您至少会覆盖 vpointer 并更改 class 类型,因此可能会在 memcpy()
之前和之后执行不同的方法。但又一次:这是否是 "misbehaviour" 取决于您的用例。您甚至可能打算更改 class 类型(包括对象到虚拟方法的映射)。
此外:从上面的代码示例中可以看出,当您 memcpy()
从动态对象 class 到非动态对象时,反之亦然,您必须了解由于注入的 vpointer 而导致的内存布局差异。特别是这个例子中 vpointer 的存在与不存在是棘手的。我的意思是,偶尔强制执行 C 风格类型的指针转换是很常见的,这可能会导致您无法按照您的想法进行转换。
另一个问题是兼容性:C++ 官方并未规定编译器应如何准确地实现动态对象。所以即使我在这里写的东西基本上适用于所有主要的编译器,正式的 C++ 甚至不需要 vtable 或 vpointers 的存在。因此从理论上讲,编译器可以自由地以完全不同的方式实现动态对象,这可能会对这个整体问题产生不同的评估。但是通过为您的软件编写测试用例可以很容易地解决这个理论问题。