面试题总结(三) -- 内存管理篇

面试题总结(三) – 内存管理篇

<1> C++ 中堆内存和栈内存的区别是什么?

在 C++ 中,堆内存和栈内存有以下区别:堆内存的分配和释放由程序员手动控制,空间较大但管理复杂;栈内存由系统自动管理,分配和释放效率高,但空间相对较小。

(1)分配方式

栈内存:

由编译器自动管理。当一个函数被调用时,函数的局部变量、参数等会在栈上分配内存。

分配和释放的过程是自动的,随着函数的调用和返回进行。例如:

void function() {
   
    int x = 10; // x 在栈上分配内存
}

一旦函数执行完毕,栈上的变量会自动被释放。

**堆内存:**由程序员手动分配和释放。使用new、malloc等操作符来分配堆内存。例如:

int* ptr = new int; // 在堆上分配一个整数的内存空间

需要使用delete、free等操作符来释放堆内存,否则会导致内存泄漏。

(2)内存大小

栈内存:

通常较小,一般在几兆字节到几十兆字节之间。不同的操作系统和编译器可能会有不同的限制。

栈内存的大小是在编译时确定的,不能动态扩展。如果在函数中声明了一个非常大的局部数组,可能会导致栈溢出错误。

堆内存:

通常较大,可以动态扩展。理论上,堆内存的大小只受限于系统的物理内存和虚拟内存的大小。

可以根据程序的需要动态地分配和释放大量的内存。

(3)生存周期

栈内存:

与函数的执行相关。当函数被调用时,栈上的变量被创建;当函数返回时,栈上的变量被销毁。

栈内存的生存周期是由编译器自动管理的,程序员无法直接控制。

堆内存:

由程序员手动控制。只要不释放堆内存,分配的内存就一直存在。

可以在程序的不同部分根据需要分配和释放堆内存,从而实现更灵活的内存管理。

(4)访问速度

栈内存:

访问速度较快。因为栈内存的分配和释放是由编译器自动管理的,并且通常在处理器的栈指针寄存器附近进行操作。

对栈内存的访问通常只需要几条机器指令即可完成。

堆内存:

访问速度相对较慢。因为堆内存的分配和释放需要操作系统的参与,并且可能涉及到内存碎片的整理等操作。

对堆内存的访问可能需要更多的机器指令和时间。

(5)数据结构

栈内存:

通常用于存储局部变量、函数参数、返回地址等。数据在栈上的存储是连续的,按照后进先出(LIFO)的顺序进行。

栈内存的分配和释放是自动的,不需要程序员手动管理,因此适用于简单的数据结构和临时变量。

堆内存:

可以用于存储更复杂的数据结构,如动态数组、链表、树等。堆内存的分配和释放是手动的,程序员可以根据需要灵活地管理内存。

可以通过指针来访问堆内存中的数据,这使得堆内存适用于需要动态分配和释放内存的数据结构。

<2> 如何在 C++ 中手动管理内存(new/delete 操作符)?

在 C++ 中,使用 new 操作符来分配内存,使用 delete 操作符来释放内存。例如:int* ptr = new int;来分配一个整数的内存空间,使用delete ptr;来释放。要注意正确匹配 new 和 delete 的使用,避免内存泄漏。

(1)使用new操作符分配内存

**动态分配单个对象:**使用new操作符可以在运行时动态地分配单个对象的内存空间。例如:

int* ptr = new int; // 分配一个整数的内存空间
*ptr = 10;

在这个例子中,new int分配了足够存储一个整数的内存空间,并返回一个指向该内存地址的指针。可以通过解引用指针来访问和修改分配的内存空间。

**动态分配数组:**new操作符也可以用于动态分配数组。例如:

int* array = new int[10]; // 分配一个包含 10 个整数的数组
for (int i = 0; i < 10; ++i) {
   
    array[i] = i;
}

在这个例子中,new int[10]分配了足够存储 10 个整数的连续内存空间,并返回一个指向数组第一个元素的指针。可以像使用普通数组一样通过下标访问和修改分配的数组元素。

(2)使用delete操作符释放内存

释放单个对象的内存:当不再需要使用通过new分配的单个对象的内存空间时,应该使用delete操作符来释放它。例如:

int* ptr = new int;
// 使用 ptr
delete ptr; // 释放 ptr 所指向的内存空间

在释放内存后,应该避免继续使用该指针,因为它可能指向无效的内存地址。

**释放数组的内存:对于通过new分配的数组,应该使用delete[]**操作符来释放内存。例如:

int* array = new int[10];
// 使用 array
delete[] array; // 释放 array 所指向的数组内存空间

使用delete[]而不是delete是很重要的,因为它会正确地调用数组中每个元素的析构函数(如果有)。

(3)注意事项

**避免内存泄漏:**在使用new分配内存后,一定要记得在适当的时候使用delete或delete[]释放内存,以避免内存泄漏。如果在程序的某个路径中忘记释放内存,分配的内存将一直占用系统资源,直到程序结束。

**处理异常:**在可能抛出异常的代码中,应该确保在异常发生时也能正确地释放内存。一种常见的方法是使用资源获取即初始化(RAII)技术,将内存的分配和释放封装在一个类中,确保在对象的生命周期结束时自动释放内存。例如:

class ResourceManager {
   
public:
    ResourceManager() : ptr(new int) {
   }
    ~ResourceManager() {
    delete ptr; }

    int* getPtr() const {
    return ptr; }

private:
    int* ptr;
};

void function() {
   
    ResourceManager manager;
    int* ptr = manager.getPtr();
    // 可能抛出异常的代码
}

在这个例子中,即使在function中发生异常,ResourceManager对象的析构函数也会被自动调用,从而确保分配的内存被正确释放。

不要重复释放内存:不要对同一个内存地址多次调用delete或delete[],这可能会导致未定义的行为。在释放内存后,应该将指针设置为nullptr,以防止意外地再次释放内存。例如:

int* ptr = new int;
delete ptr;
ptr = nullptr;
// 检查 ptr 是否为 nullptr,避免重复释放内存
if (ptr != nullptr) {
   
    delete ptr;
}

<3> C++ 中内存泄漏的原因和避免方法

在 C++ 中,内存泄漏通常是由于使用 new 分配内存后,没有使用对应的 delete 释放,或者在程序的异常处理中没有正确释放内存导致的。避免内存泄漏的方法包括:及时释放不再使用的动态分配内存、使用智能指针管理内存、在异常处理中确保内存释放等。

(1)内存泄漏的原因

忘记释放动态分配的内存,在 C++ 中,使用 new 运算符动态分配内存后,如果没有使用 delete 运算符释放该内存,就会导致内存泄漏。例如:

int* ptr = new int;
// 没有释放 ptr 所指向的内存

这种情况可能发生在复杂的程序逻辑中,特别是当代码路径分支较多时,容易忘记在所有可能的情况下释放内存。

异常导致内存泄漏,如果在动态分配内存后,在释放内存之前抛出了异常,并且没有适当的异常处理机制来确保内存被释放,就会发生内存泄漏。例如:

try {
   
    int* ptr = new int;
    // 可能抛出异常的代码
} catch (...) {
   
    // 没有释放 ptr 所指向的内存
}

在异常处理中,如果没有正确地释放已分配的内存,就会导致内存泄漏。

循环引用导致内存泄漏,在使用智能指针(如 std::shared_ptr)时,如果出现循环引用的情况,可能会导致内存泄漏。例如:

struct A;
struct B;

struct A {
   
    std::shared_ptr<B> b_ptr;
};

struct B {
   
    std::shared_ptr<A> a_ptr;
};

int main() {
   
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

螺蛳粉只吃炸蛋的走风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值