【C/C++】4.C++的内存管理

1. C++内存区域

     C++程序的内存通常分为以下几部分:

    ① 代码区(Code Segment)

  • 存储程序的机器代码,即编译后的可执行代码。代码区通常是只读的,以防止代码在运行时被意外修改,确保安全性。
  • 代码区在程序加载时由操作系统分配。

    ② 全局/静态区(Data Segment)

  • 全局变量和静态变量:存储生命周期为整个程序运行时的变量,分为已初始化区和未初始化区(BSS段)。
    • 已初始化数据段:存放已初始化的全局变量和静态变量。
    • 未初始化数据段(BSS段):存放未初始化的全局变量和静态变量,系统会将其初始化为零。
  • 常量区:存储字符串字面量和const修饰的全局或静态变量等,这些内容在程序运行期间不可修改。

    ③ 堆区(Heap Segment)

  • 堆区用于动态内存分配(如使用newmalloc),由程序员手动管理(分配和释放)。
  • 堆的增长方向通常是从低地址向高地址增长。由于堆区不受程序自动管理,如果分配的内存未被释放,可能会导致内存泄漏
  • C++的new操作符会从堆中分配内存,并在其不再使用时调用delete释放。

    ④ 栈区(Stack Segment)

  • 栈区用于存储局部变量、函数参数和返回地址等数据,每当函数被调用时分配内存,函数结束时自动释放。
  • 栈的增长方向通常是从高地址向低地址增长。
  • 栈内存由操作系统管理,具有分配速度快的特点,但容量有限,栈溢出可能导致程序崩溃。
  • 栈的布局一般包括栈帧、返回地址和参数信息等,用于维护函数调用的上下文。
  • 栈的分配速度快,堆则稍慢,但堆的容量通常大于栈。

    内存布局示意图:

    


2. 内存管理方式

2.1 栈上分配

    栈上的内存分配和释放是自动完成的,不需要程序员手动管理。栈内存用于存储局部变量、函数参数等临时数据。

 
  void exampleFunction() {
     int x = 10; // x 是栈上的局部变量
     int y = 20; // y 是栈上的局部变量
  } // exampleFunction结束后,x 和 y 的内存会自动释放

2.2 堆上分配

    堆上内存分配是动态的,需要程序员手动分配和释放。new运算符用于在堆上分配内存,delete用于释放内存。

 
  void exampleFunction() {
    int* ptr = new int(10); // 动态分配一个int类型的内存,初始值为10
    delete ptr; // 释放内存
  }
  • 动态数组分配
     
    int* array = new int[5]; // 分配一个包含5个整数的数组
    delete[] array; // 使用 delete[] 释放数组

注意:

     如果没有及时使用delete释放内存,就会导致内存泄漏(memory leak)。这是因为在程序运行期间,未释放的内存将无法被再次使用。


3. 智能指针(Smart Pointers)

     在现代C++中,智能指针用于简化内存管理,减少内存泄漏的风险。C++11提供了几种常见的智能指针:

  • std::unique_ptr
    独占式所有权,表示该对象只有一个所有者,离开作用域时会自动释放内存。

     
    #include <memory>
    std::unique_ptr<int> ptr = std::make_unique<int>(10); // 自动释放内存
  • std::shared_ptr
    共享所有权,可以有多个指针指向同一个对象。当最后一个指向对象的shared_ptr被销毁时,对象的内存才会被释放。

     
    #include <memory>
    std::shared_ptr<int> ptr1 = std::make_shared<int>(10);
    std::shared_ptr<int> ptr2 = ptr1;   // ptr1 和 ptr2共享同一个对象
  • std::weak_ptr
    辅助shared_ptr使用,不会增加对象的引用计数,常用于解决shared_ptr循环引用的问题。

     
    #include <memory>
    std::shared_ptr<int> sharedPtr = std::make_shared<int>(10);
    std::weak_ptr<int> weakPtr = sharedPtr;  // weakPtr不会影响sharedPtr的引用计数

4. 常见的内存管理问题

4.1 内存泄漏(Memory Leak)

      内存泄漏是指程序在堆上分配了内存,但没有释放,导致内存永久占用。长时间运行的程序(如服务器)如果出现内存泄漏,可能会耗尽系统内存。

      示例:

 
   void memoryLeakExample() {
       int* ptr = new int(10); // 忘记释放 ptr,导致内存泄漏
   }

4.2 悬空指针(Dangling Pointer)

      悬空指针指的是指向已经被释放内存的指针,若尝试访问该内存会导致未定义行为。

      示例:

 
   void danglingPointerExample() {
      int* ptr = new int(10);
      delete ptr; // 释放内存
      // ptr 现在是悬空指针
   }

4.3 野指针(Wild Pointer)

      野指针是指未初始化的指针,其指向未知的内存地址,容易导致程序崩溃。

      示例:

 
   void wildPointerExample() {
       int* ptr; // 未初始化,ptr是野指针
       *ptr = 10; // 可能导致程序崩溃
   }

5. C++ RAII原则

      RAII(Resource Acquisition Is Initialization,资源获取即初始化)是一种重要的内存管理原则。
      它强调在构造对象时获取资源,并在对象销毁时释放资源。智能指针和标准库容器(如std::vector)都遵循RAII原则,可以自动管理内存的分配和释放,减少了手动管理的麻烦。遵循RAII原则可以有效减少内存管理的错误,提高程序的安全性和健壮性。

     示例:

 
  #include <iostream>
  #include <vector>
  void raiiExample() {
     std::vector<int> vec = {1, 2, 3, 4, 5}; // 使用 vector 自动管理内存
     // vector 离开作用域时自动释放内存
  }
posted @ 2024-10-29 17:53  朝槿yys  阅读(149)  评论(0编辑  收藏  举报