【问题标题】:Segfault when calling virtual function of derived class调用派生类的虚函数时的段错误
【发布时间】:2013-03-31 02:39:08
【问题描述】:

我在调用派生类的虚函数时遇到了段错误问题。但是,如果我将函数的名称更改为与基类中的虚拟函数的名称不同,则不会发生这些段错误。这是一些代码:

//in main
//initialize scene objects
//camera
if((camera = (Camera*)malloc(sizeof(Camera))) == NULL){
  cout << "Could not allocate memory for camera" << endl;
}
//...code in middle
//inside file parsing...
//infile is an ifstream
//nextString is a char*
if(!strcmp(nextString,"camera")){
  camera->parse(infile); //segfault here
}

这是基类的头文件(.cpp 只在构造函数中实例化变量):

class WorldObj{
public:
  WorldObj();
  ~WorldObj();
  virtual void parse(ifstream&) =0;
  vec3 loc; //location
};

这是我用来编写虚函数的 Camera 类中的代码:

void Camera::parse(ifstream &infile){
  //do parsing stuff
}

parse()在头文件中声明为virtual void parse(ifstream&);

我的问题是,如果我将 Camera 中的 parse() 重命名为 CameraParse() 之类的名称,并且完全忽略了要实现的虚函数这一事实,那么代码完全可以正常工作!

您能否解释一下为什么调用虚函数会导致段错误?我检查了 Valgrind 是否有任何内存问题,它告诉我有 8 个字节的无效读/写。我理解这意味着我没有为我的对象正确分配内存,但我不知道分配哪里出错了。

任何帮助将不胜感激:)

【问题讨论】:

  • 没有从基类的构造函数中调用那个 virtual,是吗?
  • 尝试将 Camera 类中的解析函数标记为虚拟,以作为覆盖函数的正确方法

标签: c++ function inheritance segmentation-fault virtual


【解决方案1】:

你不能(仅仅)malloc 一个非 POD 对象,你必须 new 它。

这是因为malloc 保留了适量的空间,但不构造对象,这对于任何具有虚函数的类来说都是不平凡的,即使构造函数是默认的。

现在,具体问题只在你进行虚函数调用时出现,因为这取决于new 执行的额外初始化,但使用任何非 POD 类型的未构造实例仍然是错误的.


请注意,我使用 POD(Plain Old Data)作为任何只有微不足道的初始化的东西的惰性速记。一般而言,如果一个类(或结构)或其任何成员或基类都没有做某事的构造函数,则它是可以简单初始化的。出于我们的目的,每个具有一个或多个虚拟方法的类(即使它们是继承的,或者在数据成员中)都需要进行非平凡的初始化。

具体来说,Ben Voigt 的回答中的标准引用描述了开始对象生命周期的两个阶段(您可以安全地进行方法调用,尤其是虚拟方法调用的时间):

  • 获得了适合类型 T 的具有正确对齐和大小的存储,

当你打电话给malloc时会发生这种情况

  • 如果对象有非平凡初始化,则其初始化完成

当您使用new 时,发生在非平凡初始化类型。


作为参考,这是最接近您现有代码的正常用法:

Camera *camera = new Camera;
// don't need to check for NULL, this will throw std::bad_alloc if it fails
camera->parse(file);
// don't forget to:
delete camera;

不过,这是更好的风格:

std::unique_ptr<Camera> camera(new Camera);
camera->parse(file);
// destruction handled for you

只有当你真的需要使用malloc或其他一些特定的分配器:

Camera *camera = (Camera *)malloc(sizeof(*camera));
new (camera) Camera; // turn your pointer into a real object
camera->parse(file);
// destruction becomes uglier though
camera->~Camera();
free(camera);

【讨论】:

  • 对...如果他绝对需要与malloc 兼容,他可以写camera = new Camera(malloc(sizeof Camera));,但在大多数情况下,最好只写camera = new Camera();。使用std::nothrow,如果他想继续使用NULL 检查来检测分配失败。
  • 另外,实际上并不取决于类型是否为POD,而是初始化是否非平凡。
  • 是的,我倾向于使用 POD 作为一种懒惰的速记。
  • 我不敢相信这甚至没有发生在我身上。不睡觉编码的问题...谢谢大家:)
【解决方案2】:

使用 new,而不是 malloc。您分配了内存,但没有创建对象。 使用 new 时,将使用相应的构造函数创建虚拟函数调度表,然后才能使用它。例如:

if((camera = new (nothrow) Camera()) == NULL){
  cout << "Could not allocate memory for camera" << endl;
}

if(!strcmp(nextString,"camera")){
  camera->parse(infile); //segfault here
}

或者你可以使用简单的

camera = new Camera;

在某处捕获可能的异常 bad_alloc。

【讨论】:

  • camera 永远不会是NULL,所以条件没用。
  • @Ben Voigt。我的编辑速度不是很快 :-( 我想给出测试 NULL 的类似方法。但我认为还可以。或者?
【解决方案3】:

malloc 不调用构造函数。

你必须改变(C++创建对象的方式)

camera = (Camera*)malloc(sizeof(Camera)))

camera = new Camera; // Now you camera object will be created and constructed.

【讨论】:

    【解决方案4】:

    Useless 提供了正确的解释。以下是标准中的要求(第 3.8 节):

    对象的生命周期是对象的运行时属性。如果一个对象是一个类或聚合类型,并且它或它的一个成员是由一个普通默认构造函数以外的构造函数初始化的,则称该对象具有非普通初始化。 [注意:由普通的复制/移动构造函数初始化是非普通的初始化。 ——尾注]

    T 类型对象的生命周期开始于:

    • 获得了具有适合T类型的正确对齐和大小的存储,

    • 如果对象有非平凡的初始化,它的初始化就完成了。

    T 类型对象的生命周期在以下时间结束:

    • 如果T 是具有非平凡析构函数的类类型,则析构函数调用开始,或者

    • 对象占用的存储空间被重用或释放。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-09-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多