当前位置: 首页 > news >正文

c/c++-memory-management

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/freenew/delete
1malloc/free是函数new/delete是运算符
2malloc申请的空间不会初始化new可以初始化
3malloc申请空间需要手动计算大小new T[]中指定个数即可
4malloc返回void*,需要类型转换new指定类型,无需类型转换
5malloc失败返回NULLnew失败抛异常
6对于自定义类型,malloc/free不会调用构造和析构函数new/delete申请/释放空间,会调用构造和析构函数
http://www.xdnf.cn/news/1157563.html

相关文章:

  • 【PTA数据结构 | C语言版】是不是堆
  • SpringBoot集成Skywalking链路跟踪
  • 2025年渗透测试面试题总结-2025年HW(护网面试) 59(题目+回答)
  • 奥比中光双目摄像头实现物品抓取的机器人系统
  • 【Lua】多脚本引用
  • 数据结构 | 栈:构建高效数据处理的基石
  • Docker Compose
  • LeetCode 198 打家劫舍 LeetCode 213.打家劫舍II
  • Kotlin函数式接口
  • 力扣:动态规划java
  • kotlin Flow快速学习2025
  • 算法训练营DAY36 第九章 动态规划part04
  • Request和Response相关介绍
  • 数字图像处理(四:图像如果当作矩阵,那加减乘除处理了矩阵,那图像咋变):从LED冬奥会、奥运会及春晚等等大屏,到手机小屏,快来挖一挖里面都有什么
  • 《计算机网络》实验报告三 UDP协议分析
  • STM32-第八节-TIM定时器-4(编码器接口)
  • C++虚函数易错点整理
  • Python dataclass 高阶用法与技巧
  • springboot-profile
  • Direct3D 11学习(一)
  • 数学专业转行做大数据容易吗?需要补什么?
  • Web服务压力测试工具hey学习一:使用方法
  • 如何解决pip安装报错error subprocess-exited-with-error问题
  • 力扣面试150题--搜索插入位置
  • 30天打牢数模基础-灰色预测模型讲解
  • BLIP、InternVL Series(下)
  • Eureka+LoadBalancer实现服务注册与发现
  • JavaScript 对象操作、继承与模块化实现
  • RCE随笔(1)
  • 使用 Pyecharts 绘制精美饼状图:从基础到高级技巧