【C++】C/C++内存管理(超详细解析,小白必看系列)

目录

1 C/C++内存分布

2. C语言中动态内存管理方式:

3. C++内存管理方式

4. operator new与operator delete函数

5. new和delete的实现原理

6. 定位new表达式(placement-new)

 


1 C/C++内存分布

1.1 内存分布介绍

在C/C++程序中,内存分布通常分为以下几个主要部分:

  1. 代码段(Text Segment):存储程序的可执行代码。
  2. 数据段(Data Segment)
    • 已初始化数据段(Initialized Data Segment):存储已初始化的全局和静态变量。
    • 未初始化数据段(Uninitialized Data Segment 或 BSS Segment):存储未初始化的全局和静态变量。
  3. 堆(Heap):用于动态内存分配,程序运行时通过malloccallocrealloc等函数分配的内存。
  4. 栈(Stack):存储局部变量和函数调用信息(如返回地址、参数等)。

这种内存布局有助于程序的高效运行和内存管理。

1.2 代码介绍

int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
 static int staticVar = 1;
 int localVar = 1;
 int num1[10] = { 1, 2, 3, 4 };
 char char2[] = "abcd";
 const char* pChar3 = "abcd";
 int* ptr1 = (int*)malloc(sizeof(int) * 4);
 int* ptr2 = (int*)calloc(4, sizeof(int));
 int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
 free(ptr1);
 free(ptr3);
}

2. C语言中动态内存管理方式:

在 C 语言中,动态内存管理是通过几个标准库函数来实现的,这些函数主要包括 malloccallocreallocfree。这些函数都在 <stdlib.h> 头文件中定义。以下是它们的主要功能和用法:

  1. malloc 函数

    • 用于在堆区分配一块指定大小的内存空间。
    • 语法:void* malloc(size_t size);
    • 返回一个指向分配内存的指针,如果分配失败则返回 NULL
    • 示例:
      int *ptr = (int*)malloc(10 * sizeof(int));
      if (ptr == NULL) {
          // 处理内存分配失败
      }
      
  2. calloc 函数

    • 用于在内存中分配多个元素,并将每个元素初始化为零。
    • 语法:void* calloc(size_t num, size_t size);
    • 返回一个指向分配内存的指针,如果分配失败则返回 NULL
    • 示例:
      int *ptr = (int*)calloc(10, sizeof(int));
      if (ptr == NULL) {
          // 处理内存分配失败
      }
      
  3. realloc 函数

    • 用于重新分配内存,把内存块扩展到新的大小。
    • 语法:void* realloc(void* ptr, size_t new_size);
    • 返回一个指向重新分配内存的指针,如果分配失败则返回 NULL
    • 示例:
      ptr = (int*)realloc(ptr, 20 * sizeof(int));
      if (ptr == NULL) {
          // 处理内存分配失败
      }
      
  4. free 函数

    • 用于释放先前通过 malloccallocrealloc 分配的内存。
    • 语法:void free(void* ptr);
    • 示例:
      free(ptr);
      ptr = NULL; // 避免悬空指针
      

3. C++内存管理方式

3.1 new/delete操作内置类型

在 C++ 中,newdelete 操作符用于动态内存管理,特别是内置类型的内存分配和释放。以下是它们的基本用法和注意事项:

使用 new 操作符

  1. 分配单个内置类型的内存

    int* ptr = new int; // 分配一个 int 类型的内存
    *ptr = 10; // 初始化
    
  2. 分配并初始化单个内置类型的内存

    int* ptr = new int(10); // 分配并初始化为 10
    
  3. 分配数组内置类型的内存

    int* arr = new int[10]; // 分配一个包含 10 个 int 类型元素的数组
    

使用 delete 操作符

  1. 释放单个内置类型的内存

    delete ptr; // 释放单个 int 类型的内存
    
  2. 释放数组内置类型的内存

    delete[] arr; // 释放数组内存
    

注意事项

  • 异常处理new 操作符在内存分配失败时会抛出 std::bad_alloc 异常,而 malloc 则返回 NULL。因此,使用 new 时可以通过 try-catch 块来处理异常。

    try {
        int* ptr = new int[1000000000]; // 可能会失败
    } catch (std::bad_alloc& e) {
        std::cerr << "Memory allocation failed: " << e.what() << std::endl;
    }
    
  • 避免内存泄漏:确保每个 new 对应一个 delete,每个 new[] 对应一个 delete[],以避免内存泄漏。

  • 初始化:使用 new 分配的内存未初始化(除非显式初始化),而 new int(10) 这样的语法可以直接初始化内存。

3.2 new/delete操作内置类型

在 C++ 中,newdelete 操作符不仅可以用于内置类型,还可以用于自定义类型(类)。以下是如何使用 newdelete 来管理自定义类型的内存:

使用 new 操作符

  1. 分配单个自定义类型对象的内存

    class MyClass {
    public:
        MyClass(int value) : data(value) {}
        ~MyClass() {}
    private:
        int data;
    };
    
    MyClass* obj = new MyClass(10); // 调用构造函数
    
  2. 分配数组自定义类型对象的内存

    MyClass* objArray = new MyClass[3]; // 调用默认构造函数
    

使用 delete 操作符

  1. 释放单个自定义类型对象的内存

    delete obj; // 调用析构函数
    
  2. 释放数组自定义类型对象的内存

    delete[] objArray; // 调用每个对象的析构函数
    

注意事项

  • 构造函数和析构函数:使用 new 分配内存时,会自动调用构造函数进行初始化;使用 delete 释放内存时,会自动调用析构函数进行清理。
  • 数组分配:当使用 new[] 分配数组时,必须使用 delete[] 释放内存,以确保每个对象的析构函数都被正确调用。
  • 异常处理new 操作符在分配失败时会抛出 std::bad_alloc 异常,可以使用 try-catch 块来处理。

示例代码

#include <iostream>

class MyClass {
public:
    MyClass(int value = 0) : data(value) {
        std::cout << "Constructor called with value: " << data << std::endl;
    }
    ~MyClass() {
        std::cout << "Destructor called for value: " << data << std::endl;
    }
private:
    int data;
};

int main() {
    // 分配单个对象
    MyClass* obj = new MyClass(10);
    delete obj;

    // 分配对象数组
    MyClass* objArray = new MyClass[3];
    delete[] objArray;

    return 0;
}

4. operator newoperator delete函数

在 C++ 中,newdelete是用户进行动态内存申请和释放的操作符operator new operator delete是系统提供的全局函数new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。

operator new 函数

  1. 基本用法

    /*
    operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空               间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
    */
    void*__CRTDECLoperatornew(size_tsize) _THROW1(_STDbad_alloc)
    {// try to allocate size bytes
        void*p;
        while ((p=malloc(size)) ==0)
         if (_callnewh(size) ==0)
         {
             // report no memory
             // 如果申请内存失败了,这里会抛出bad_alloc 类型异常                             
             staticconststd::bad_allocnomem;
             _RAISE(nomem);
         }
        return (p);
    }
    
    • 该函数分配一块大小为 size 字节的内存,并返回一个指向这块内存的指针。
    • 如果分配失败,默认情况下会抛出 std::bad_alloc 异常。
  2. 示例

    void* ptr = operator new(sizeof(int) * 10); // 分配 10 个 int 的内存
    
  3. 重载

    • 可以重载 operator new 以自定义内存分配行为。例如:
      void* MyClass::operator new(size_t size) {
          void* p = ::operator new(size);
          // 自定义初始化或日志记录
          return p;
      }
      

operator delete 函数

  1. 基本用法

    void operator delete(void *pUserData)
    {
         _CrtMemBlockHeader * pHead;
         RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
         if (pUserData == NULL)
             return;
         _mlock(_HEAP_LOCK);  /* block other threads */
         __TRY
             /* get a pointer to memory block header */
             pHead = pHdr(pUserData);
              /* verify block type */
             _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
             _free_dbg( pUserData, pHead->nBlockUse );
         __FINALLY
             _munlock(_HEAP_LOCK);  /* release other threads */
         __END_TRY_FINALLY
         return;
    }
    /*
    free的实现
    */
    #define   free(p)               _free_dbg(p, _NORMAL_BLOCK)
    
    • 该函数释放由 operator new 分配的内存。
    • pUserData 是指向要释放的内存的指针。
  2. 示例

    operator delete(ptr); // 释放内存
    
  3. 重载

    • 可以重载 operator delete 以自定义内存释放行为。例如:
      void MyClass::operator delete(void* ptr) {
          // 自定义清理或日志记录
          ::operator delete(ptr);
      }
      

operator new[]operator delete[]

  • 数组分配和释放
    void* operator new;
    void operator delete;
    
    • 这些函数用于分配和释放数组内存,与 operator newoperator delete 类似,但用于数组。

示例代码

#include <iostream>
#include <new> // 包含 operator new 和 operator delete 的声明

class MyClass {
public:
    MyClass() { std::cout << "Constructor called\n"; }
    ~MyClass() { std::cout << "Destructor called\n"; }

    void* operator new(size_t size) {
        std::cout << "Custom operator new\n";
        return ::operator new(size);
    }

    void operator delete(void* ptr) {
        std::cout << "Custom operator delete\n";
        ::operator delete(ptr);
    }
};

int main() {
    MyClass* obj = new MyClass(); // 调用自定义的 operator new
    delete obj; // 调用自定义的 operator delete

    return 0;
}

5. newdelete的实现原理

5.1 内置类型

如果申请的是内置类型的空间, new malloc delete free 基本类似,不同的地方是:
new/delete 申请和释放的是单个元素的空间, new[] delete[] 申请的是连续空间,而且 new 在申
请空间失败时会抛异常, malloc 会返回 NULL

5.2 自定义类型

在 C++ 中,newdelete 操作符用于动态内存管理,特别是自定义类型(类)的内存分配和释放。它们的实现原理与内置类型类似,但涉及构造函数和析构函数的调用。以下是它们的实现原理和示例代码:

new 操作符的实现原理

  1. 内存分配

    • new 操作符在底层调用 operator new 函数来分配内存。
    • operator new 通常使用 malloc 来分配内存。
    • 如果内存分配失败,operator new 会抛出 std::bad_alloc 异常。
  2. 对象初始化

    • 对于自定义类型,new 操作符会调用构造函数来初始化对象。
    • 这一步是 new 操作符与 malloc 函数的主要区别之一。

delete 操作符的实现原理

  1. 对象清理

    • 对于自定义类型,delete 操作符会调用析构函数来清理对象。
    • 这一步在释放内存之前进行,以确保对象的资源被正确释放。
  2. 内存释放

    • delete 操作符在底层调用 operator delete 函数来释放内存。
    • operator delete 通常使用 free 来释放内存。

示例代码

以下是一个自定义类 MyClass,并展示了 newdelete 的使用:

#include <iostream>
#include <new> // 包含 operator new 和 operator delete 的声明

class MyClass {
public:
    MyClass(int value) : data(value) {
        std::cout << "Constructor called with value: " << data << std::endl;
    }
    ~MyClass() {
        std::cout << "Destructor called for value: " << data << std::endl;
    }
private:
    int data;
};

int main() {
    // 使用 new 分配单个对象
    MyClass* obj = new MyClass(10); // 调用构造函数
    delete obj; // 调用析构函数并释放内存

    // 使用 new 分配对象数组
    MyClass* objArray = new MyClass[3]; // 调用默认构造函数
    delete[] objArray; // 调用析构函数并释放数组内存

    return 0;
}

代码解释

  • 构造函数和析构函数:在分配和释放内存时,分别调用构造函数和析构函数。
  • 自定义 operator newoperator delete:可以通过重载这些操作符来自定义内存分配和释放行为。

6. 定位new表达式(placement-new) 

在 C++ 中,定位 new 表达式(placement new)是一种特殊的内存分配方式,它允许在已经分配好的内存上构造对象。通常情况下,new 操作符会在堆上分配内存并调用构造函数来初始化对象,而定位 new 允许我们在已有的内存空间上构造对象。

基本语法

void* operator new(size_t size, void* ptr) noexcept;

  • size:要分配的内存大小,通常是对象的大小。
  • ptr:指向已分配好的内存空间的指针。

示例代码

以下是一个使用定位 new 的示例:

#include <iostream>
#include <new> // 包含 placement new 的声明

class MyClass {
public:
    MyClass(int value) : data(value) {
        std::cout << "Constructor called with value: " << data << std::endl;
    }
    ~MyClass() {
        std::cout << "Destructor called for value: " << data << std::endl;
    }
private:
    int data;
};

int main() {
    // 预先分配内存
    char buffer[sizeof(MyClass)];

    // 使用定位 new 在已有内存上构造对象
    MyClass* obj = new (buffer) MyClass(42);

    // 调用对象的方法
    obj->~MyClass(); // 显式调用析构函数

    return 0;
}

注意事项

  1. 手动调用析构函数:使用定位 new 构造的对象需要手动调用析构函数,因为 delete 不会释放由定位 new 分配的内存。
  2. 内存管理:确保 ptr 指向的内存足够大且未被其他对象占用,否则可能导致未定义行为。
  3. 内存对齐:确保 ptr 指向的内存地址满足对象的对齐要求。

应用场景

定位 new 常用于内存池或固定内存区域中管理对象,以提高性能和内存管理的效率

  • 18
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值