C++内存管理

1 内存分布

代码区:用于存储程序代码的区域,代码段在程序真正执行前就被加载到内存中,在程序执行期间,代码区内存不会被修改和释放。由于代码区是只读的,所以会被多个进程共享,在多个进程同时执行同一个程序时,操作系统只需要将代码段加载到内存中一次,然后让多个进程共享这个内存区域即可。

数据段:用于存储静态全局变量、静态局部变量和静态常量等静态数据,在程序运行期间,数据段的大小固定不变,但其内容可以被修改。按照变量是否被初始化,数据段可分为已初始化数据段和未初始化数据段。

栈:C++中函数调用以及函数内的局部变量,都是通过栈这个内存分区实现的。栈分区由操作系统自动分配和释放,是一种后进先出的一种内存分区。每个栈的大小是固定的,一般只有几MB,如果栈变量太大,或者函数调用嵌套太深,容易发生栈溢出。

具体实例如下:

#include 

void inner(int a) {
    std::cout << a << std::endl;
}
void outer(int n) {
 int a = n + 1;
    inner(a);
}

int main() {
    outer(4);
}

上面这段代码运行过程中的栈变化如下图:

C++内存管理_第1张图片

每当程序调用一个函数时,该函数的参数、局部变量和返回地址等信息会被压入栈中。当函数执行完毕,再将这些信息从栈中弹出。根据之前压入的外层调用者压入栈的返回地址,返回到外层调用者未执行的代码继续执行。本地变量直接存储在栈上,当函数执行完成后,这些变量占用的内存就会被释放掉了。前面例子中的本地变量是简单类型,在C++中成为POD类型,对于带有构造和析构函数的非POD类型变量,栈上的内存分配同样有效,编译器会在合适的时机,插入对构造函数和析构函数的调用。

堆:堆是C++中用来存储动态分配内存的内存分区,堆内存的分配和释放需要手动管理,可以通过new/delete或malloc/free等函数进行分配和释放。堆内存的大小通常是不固定的,当我们需要动态分配内存时,就可以使用堆内存。堆内存由程序员手动分配和释放,因此使用堆内存需要注意内存泄漏和内存溢出等问题。当程序员忘记释放已分配的内存时,会导致内存泄漏问题。而当申请的堆内存超过了操作系统所分配给进程的内存限制时,会导致内存溢出问题。C++程序绝大多数的内存泄露,都是由于忘记调用delete/free来释放堆上的资源。

 RAII:在堆上创建对象,但不想处理这么复杂的内存释放操作,C++没有像java、golang其他语言创建一套垃圾回收机制,而是采用了一种特有的资源管理方式。RAII:资源获取即初始化。RAII利用栈对象在作用域结束后会自动调用析构函数的特点,通过创建栈对象来管理资源。在栈对象构造函数中获取资源,在栈对象析构函数中负责释放资源,以此保证资源的获取和释放。

下面给出一个通过RAII来自动释放堆内存的例子

#include 

class AutoIntPtr {
public:
    AutoIntPtr(int* p = nullptr) : ptr(p) {}
    ~AutoIntPtr() { delete ptr; }

    int& operator*() const { return *ptr; }
    int* operator->() const { return ptr; }

private:
    int* ptr;
};

void foo() {
 AutoIntPtr p(new int(5));
    std::cout << *p << std::endl; // 5
}

int main() {
    foo();
}

上面例子中,AutoIntPtr类封装了一个动态分配的int类型的指针,它的构造函数用于获取资源(ptr = p),析构函数用于释放资源(delete ptr)。当AutoIntPtr超出作用域时,自动调用析构函数来释放所包含的资源。基于RAII,C++11引入了std::unique_ptrstd::shared_ptr等智能指针用于内存管理类,使得内存管理变得更加方便和安全。这些内存管理类可以自动进行内存释放,避免了手动释放内存的繁琐工作。值得一提的是,上面的AutoIntPtr就是一个简化版的智能指针了。

在实际开发中,RAII的应用很广。不仅仅用于自动释放内存。还可以用来关闭文件、释放数据库连接、释放同步锁等。

你可能感兴趣的:(c++,c++)