c/c++-memory-management

发布于:2025-07-21 ⋅ 阅读:(15) ⋅ 点赞:(0)

C/C++内存管理

1. 内存分布

  1. :系统自动管理,通常存储函数局部变量。

    • 栈是向下增长的,大小通常几MB

    • 申请释放速度快

  2. :手动申请和释放。

    • 堆是向上增长的,可用空间大

    • 申请释放速度慢

  3. 数据段:存储全局变量和静态变量。

    • 全局数据又分为已初始化和未初始化
  4. 代码段:存储正文代码和常量。

    • 代码段通常是只读的
  5. 共享区:通常是多个进程可以共同访问的内存区域。

    • 进程间通信,允许多个进程读写同一块内存,高效传递数据

    • 动态库的加载,动态库在内存只需加载一次,多个进程可以共享

在这里插入图片描述


2. C

函数原型 作用
void *malloc( size_t size ); 分配连续的指定字节数的未初始化内存
void *calloc( size_t num, size_t size ); 分配 num 个大小为 size 的连续内存空间,并初始化为0
void *realloc( void *memblock, size_t new_size ); 调整之前分配内存块的大小,new_size 新的字节数
void free( void *memblock ); 释放之前分配的内存
  • 函数统一头文件:#include <stdlib.h>

  • malloc

    • 分配失败,返回 NULL
  • calloc

    • malloc 开辟的空间存放随机值,而 calloc 开辟后空间初始化为 0

    • 分配失败,返回 NULL

  • realloc

    • 分配失败,返回 NULL

    • 原地扩容:新空间比原空间大,原空间后续的空间足够新空间的大小,则直接在原空间的尾部进行扩容,返回原空间的起始地址。

    • 异地扩容:新空间比原空间大,原空间后续的空间不够新空间的大小,realloc 则会找一个足够新空间大小的内存空间进行分配,并将原空间的数据拷贝过来,释放原空间,返回新空间的起始地址。

    • realloc 第一个参数传 NULL ,则等价 malloc

  • free

    • 释放之前分配的内存。

    • 常见的动态内存错误。

      • NULL 解引用

      • 非动态开辟内存使用 free 释放

      • free 释放不完整的堆内存

      • 同一块内存多次释放

3. C++

在C++中,由于引入了类,继续使用C语言风格的malloc/free内存管理机制存在 根本性的缺陷。 主要体现在对于自定义类型无法调用构造和析构函数。因此 C++通过 new 和 *delete 运算符进行动态内存管理。

3.1 new与delete运算符的使用

3.1.1 动态申请
// 动态申请
int* ptr = new int;
ClassName* ptr = new ClassName;    // 调用默认构造函数
3.1.2 动态申请并初始化
// 动态申请并初始化
int* ptr = new int(1);
ClassName* ptr = new ClassName(arg1, arg2);  // 调用带参构造函数
3.1.3 释放变量/对象
// 释放对象
delete ptr;
3.1.4 动态申请数组
// 动态申请数组
int* ptr = new int[3];
ClassName* ptr = new ClassName[3];
3.1.5 动态申请数组并初始化
// 动态申请数组并初始化
int* ptr = new int[3]{1 , 2 , 3};
ClassName* ptr = new ClassName[3]{ClassName() , ClassName() , ClassName()};
3.1.6 释放数组
// 释放数组
delete[] ptr;

申请和释放自定义类型(对象)时,new会调用构造函数,delete会调用析构函数。

3.2 operator new与operator delete函数

operator newoperator delete 是系统提供的全局运算符重载函数。

  • operator new 函数实际通过 malloc 申请空间,但是当申请空间失败时,会 抛异常

  • operator delete 函数实际通过 free 释放空间。

3.3 new和delete的实现原理

如果申请的是内置类型,new和malloc,delete和free基本类似,而申请对象是自定义类型时,new和delete会自动调用构造函数和析构函数

3.3.1 new原理
  1. 调用 operator new 函数申请空间。

  2. 在申请的空间上调用该对象的构造函数。

3.3.2 delete原理
  1. 调用该对象的析构函数。

  2. 调用 operator delete 函数释放对象空间。

3.3.3 new T[N]原理
  1. new 调用 operator new[],operator new[] 中实际调用 operator new 函数完成对N个对象申请空间。

  2. 在申请的空间上执行N次构造函数。

3.3.4 delete[] 原理
  1. 在释放的空间上执行N次析构函数。

  2. delete 调用 operator delete[],operator delete[] 中实际调用 operator delete 函数完成对N个对象释放空间。

3.4 delete对new []是为定义行为?

先来看一段代码:以下代码 delete aptr 程序崩溃了,而 delete bptr 程序没有崩溃,这是为什么?

// vs 2019
#include <iostream>
using namespace std;

class A {
public:
    ~A() {
        cout << "aaa" << endl;
    }
    int a;
};

class B {
public:
    int b;
};

int main() {

    // 崩溃
    //A* aPtr = new A[3];
    //delete aPtr;

    // 正常运行
    B* bPtr = new B[3];
    delete bPtr;

    return 0;
}

分析malloc和new底层原理

  • malloc 它的底层实现会在分配的内存块前添加一个管理用的头信息(通常称为 malloc_header)。

    • 内存块的大小(这也就是为什么调用free时,不用传递空间大小)。

      • 调用 free 时,通过 ptr - sizeof(malloc_header) 释放完整的内存
    • 其他信息。
      在这里插入图片描述

  • new 的底层也是调用 malloc 开辟空间的,而 new 也会记录自己管理用的头信息尤其是 new []

    • new [] 添加 size_t N 记录析构的次数

    • 其他信息。

  • new尤其是 new[])不会在 malloc_header 前面插入数据,而是在 malloc 返回的可用内存空间的开头部分存储自己的信息sizeof(T) * N + 额外空间 )。
    在这里插入图片描述

  • new T[N](数组,T 需析构),添加 size_t N 头信息。new T[N](数组,T 无需析构),优化为无头信息(直接 malloc)。

    • 当没有显示写析构函数,编译器可能会直接优化掉。
  • new T[N] 如果 T 需要析构

    • delete ptr 会错误地认为 ptr 指向单个对象,而非数组,忽略了 size_t 头信息,导致 delete 底层 free 时释放不完整的空间(而且只会调用一次析构函数)。

      • 实际申请:malloc_header + size_t + ptr(用户实际空间)

      • 误以为释放:ptr - malloc_header

      • 正确释放:ptr - size_t - malloc_header

  • new T[N] 如果 T 无需析构

    • delete ptr 可能“侥幸”底层调用 free 释放成功(因无头信息),但仍是 未定义行为

严格匹配 new/deletenew[]/delete[]

3.5 定位new表达式(placement-new)

placement-new 表达式是在 已分配的内存空间中调用构造函数初始化一个对象,核心作用是 将内存分配和对象构造分离

  • 普通 new :分配内存 + 调用构造函数。

  • placement-new:仅调用构造函数,内存由外部管理。

  • new(place_address)type(arg…)

    • place_address:已分配的内存指针

    • type:类型

    • arg:构造参数

A* ptr = (A*)operator new(sizeof(A));    // 开辟内存但是没有调用构造函数
new(p2)A(10);                            // placement-new调用构造函数
p2->~A();                                // 显式调用析构函数
operator delete(p2);                     // 释放内存   

4. malloc/free与new/delete的区别

malloc/free new/delete
1 malloc/free是函数 new/delete是运算符
2 malloc申请的空间不会初始化 new可以初始化
3 malloc申请空间需要手动计算大小 new T[]中指定个数即可
4 malloc返回void*,需要类型转换 new指定类型,无需类型转换
5 malloc失败返回NULL new失败抛异常
6 对于自定义类型,malloc/free不会调用构造和析构函数 new/delete申请/释放空间,会调用构造和析构函数

网站公告

今日签到

点亮在社区的每一天
去签到