MST

星途 面试题库

面试题:C++ 多态下析构函数调用顺序的调试与内存管理

考虑一个C++程序,其中有一个基类Shape,以及两个派生类Circle和Rectangle,它们都继承自Shape。Shape类有一个虚析构函数,Circle和Rectangle类各自有一些成员变量。现在通过一个Shape指针数组来管理多个Circle和Rectangle对象。请阐述当释放这个指针数组时,析构函数的调用顺序。如果在析构过程中出现内存泄漏,你会如何利用调试技巧(如Valgrind)定位问题所在,并说明如何通过修改代码来解决内存泄漏问题。
24.6万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试
  1. 析构函数调用顺序
    • 当释放Shape指针数组时,对于数组中的每个指针,会先调用对应对象的析构函数。由于Shape类有虚析构函数,所以会根据对象的实际类型来调用析构函数。
    • 例如,如果指针指向Circle对象,会先调用Circle类的析构函数,然后调用Shape类的析构函数。如果指针指向Rectangle对象,会先调用Rectangle类的析构函数,然后调用Shape类的析构函数。这是因为C++的析构顺序是从子类到基类。
  2. 利用Valgrind定位内存泄漏问题
    • 安装Valgrind:在Linux系统上,可以使用包管理器(如apt - get install valgrind)安装Valgrind。
    • 运行程序:使用Valgrind运行包含内存泄漏的C++程序,例如valgrind --leak - check = full./your_program
    • 分析报告:Valgrind会生成详细的报告,指出内存泄漏发生的位置。报告中会包含泄漏内存块的大小、分配内存的函数调用栈等信息。通过分析调用栈,可以找到代码中分配内存但未释放的具体位置。
  3. 修改代码解决内存泄漏问题
    • 确保正确释放资源:检查对象的析构函数,确保所有在构造函数或其他成员函数中分配的动态内存(如使用new分配的内存)在析构函数中使用delete(或delete[]对于数组)正确释放。例如,如果Circle类有一个动态分配的成员变量int* radiusPtr,在Circle的析构函数中应该有delete radiusPtr;
    • 智能指针的使用:可以将Shape指针数组中的指针替换为智能指针(如std::unique_ptr<Shape>std::shared_ptr<Shape>)。智能指针会自动管理对象的生命周期,在对象不再需要时自动调用析构函数,从而避免手动管理内存带来的泄漏风险。例如:
#include <memory>
#include <vector>

class Shape {
public:
    virtual ~Shape() = default;
};

class Circle : public Shape {
private:
    int radius;
public:
    Circle(int r) : radius(r) {}
};

class Rectangle : public Shape {
private:
    int width;
    int height;
public:
    Rectangle(int w, int h) : width(w), height(h) {}
};

int main() {
    std::vector<std::unique_ptr<Shape>> shapes;
    shapes.emplace_back(std::make_unique<Circle>(5));
    shapes.emplace_back(std::make_unique<Rectangle>(4, 6));
    // 这里智能指针会自动管理对象的析构,无需手动释放
    return 0;
}