=========================================================================

相关代码gitee自取

C语言学习日记: 加油努力 (gitee.com)

=========================================================================

接上期

【C++初阶】六、类和对象(初始化列表、static成员、友元、内部类)-CSDN博客

=========================================================================

目录

一 . C/C++内存分布

C/C++中程序内存区域划分:


二 . C++内存管理方式

回顾:C语言中动态内存管理方式malloc / calloc / realloc / free

C++的内存管理方式

new / delete –操作内置类型:

new / delete — 操作自定义类型:

常见面试题 — malloc / free 和 new / delete 的区别


三 . operator new 和 operator delete 函数

operator new / operator delete

operator new 全局函数:

operator delete 全局函数:

图示 — operator new / delete 全局函数:

new 和 delete 的实现原理

对于内置类型:

(重点)对于自定义类型:


四 . 定位new表达式(placement-new)(了解)


本篇博客相关代码

Test.cpp文件 — C++文件:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

一 . C/C++内存分布

C/C++中程序内存区域划分:

不同的数据不同的存储需求内存中有各种区域满足不同的需求

  • 堆栈):
    存放非静态局部变量 / 函数参数 / 返回值 ……,栈是向下增长
  • 内存映射段
    内存映射段是最高效的 I/O映射方式 用于装载一个共享的动态内存库
    用户可以使用系统接口创建共享内存进程间通信

  • 用于程序运行时动态内存分配堆是向上增长
    动态使用数据结构算法中需要动态开辟一些空间
  • 数据段静态区):
    操作系统角度数据段语言角度静态区
    存储全局数据静态数据
    整个程序运行期间都可能会使用到的数据
  • 代码段常量区):
    操作系统角度代码段语言角度常量区
    存储可执行代码汇编指令常量
    只读数据
图示:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

二 . C++内存管理方式

回顾:
C语言中动态内存管理方式malloc / calloc / realloc / free

之前学习C语言的时候有写过动态内存管理相关内容
有需要的话可以进行查看

学C的第三十二天【动态内存管理】_高高的胖子的博客-CSDN博客


C++的内存管理方式

C语言内存管理方式在C++中可以继续使用有些地方无能为力

而且使用起来会比较麻烦因此C++中又提出了自己的内存管理方式

通过 new delete 操作符进行动态内存管理

new / delete –操作内置类型

  • new 申请单个空间
    内置类型指针 指针名 = new 内置类型;

  • new 申请多个空间
    内置类型指针 指针名 = new 内置类型[申请单位空间个数];

  • new 申请单个空间进行初始化
    内置类型指针 指针名 = new 内置类型(初始化值);

  • new 申请多个空间进行初始化
    内置类型指针 指针名 = new 内置类型[申请单位空间个数]{第一个初始化值, 第二个初始化值……};

  • delete 释放new申请的空间
    //释放new申请的单个空间:delete 内置类型指针名;//释放new申请的多个空间:delete[] 内置类型指针名;
  • 对于内置类型的对象申请释放
    C++new / delete C语言malloc / calloc / realloc / free
    除了用法(“强转计算开辟空间大小,(底层几乎没有任何区别
图示:


———————————————————————————————

​​​​​​​
new / delete — 操作自定义类型

  • new 申请单个空间
    对于自定义类型使用C++中的new开辟动态空间的话
    会在开辟空间后顺便调用其构造函数进行自定义类型对象的初始化​​​​​​​
    //开辟单个空间并自动调用 默认构造函数 进行初始化:自定义类型指针 指针名 = new 自定义类型;//开辟单个空间并调用 有参构造函数 进行初始化:自定义类型指针 指针名 = new 自定义类型(初始化值);

  • new 申请多个空间
    对于自定义类型使用new申请多个空间
    同样会在开辟空间后顺便调用其构造函数进行自定义类型对象的初始化
    ​​​​​​​
    //方式一:通过有名对象:(先初始化多个自定义类型对象);自定义类型指针 指针名 = new 自定义类型[申请单位空间个数]{有名对象1, 有名对象2……};//方式二:通过匿名对象:自定义类型指针 指针名 = new 自定义类型[申请单位空间个数]{匿名对象1, 匿名对象2……};//方式三:通过内置类型的隐式类型转换为自定义类型:自定义类型指针 指针名 = new 自定义类型[申请单位空间个数]{内置类型1, 内置类型2……};

  • delete 释放new申请的空间
    //释放new申请的单个空间:delete 自定义类型指针名;//释放new申请的多个空间:delete[] 自定义类型指针名;
  • 对于自定义类型的对象申请释放
    C++new 除了会开辟动态空间外,还会自动调用其构造函数进行初始化
    ​​​​​​​​​​​​​​
图示:


———————————————————————————————

​​​​​​​

常见面试题 — malloc / free 和 new / delete 的区别

共同点:

malloc / free new / delete 都是从申请空间并且都需要用户手动释放


———————————————————————————————

​​​​​​​

不同点:
  • malloc free函数 new delete 操作符
  • malloc 申请的空间不会被初始化 new 申请的空间则会被初始化
  • malloc 申请空间时需要手动计算开辟的空间大小传递
    new 申请空间时只需要在其后写出空间的类型即可
    如果是多个对象[ ]中指定对象个数即可
  • malloc 返回值 void*在使用时必须进行强转
    new 则不需要因为 new 后跟的是空间的类型
  • malloc 申请空间失败时返回的是空指针NULL因此使用时必须判空
    new 则不需要但是 new 需要捕获异常
  • 在申请自定义类型对象
    malloc / free 只会开辟空间不会调用构造函数析构函数
    new 在申请空间后会调用构造函数完成对象的初始化
    delete 在释放空间前会调用析构函数完成空间中资源的清理

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

三 . operator new 和 operator delete 函数

operator new / operator delete

new delete C++中进行动态内存申请和释放操作符

operator new operator delete系统提供的全局函数

new 底层会调用 operator new 全局函数申请空间

delete 底层会调用 operator delete 全局函数释放空间

operator new 全局函数:

  • 虽然函数名中有 operator 但并不是重载函数
    ​​​​​​​
  • C语言malloc 如果申请空间失败的话返回空指针
    这不符合C++面向对象编程的要求所以需要对其进行封装
  • operator new 全局函数就是对 malloc 封装
    所以 operator new 全局函数底层会调用 malloc
    malloc 申请空间失败后会抛出异常从而能够符合C++面向对象编程的要求
    operator new 全局函数malloc 一样只会申请空间不会调用构造函数初始化


———————————————————————————————

​​​​​​​

operator delete 全局函数:

  • operator delete 全局函数同样也不是重载函数而是一个全局函数
  • operator delete 全局函数是对 free 封装
    所以 operator delete 全局函数底层会调用 free
    相较 free operator delete 全局函数多了一些检查
    operator delete 全局函数free 一样只会释放空间不会调用析构函数


———————————————————————————————

​​​​​​​

图示 — operator new / delete 全局函数​​​​​​​:


new 和 delete 的实现原理

对于内置类型:

如果申请的是内置类型对象的空间new mallocdelete free 基本类似
不同的地方是

new / delete 申请释放的是单个元素的空间new[ ] /delete[ ] 操作的则是连续的空间

而且 new 申请空间失败时会抛出异常而C语言中malloc则会返回空指针


———————————————————————————————

​​​​​​​

(重点)对于自定义类型:

  • new 的原理申请单个动态空间):

    第一步为自定义类型对象开辟动态空间调用 operator new 全局函数
    new => operator new => malloc

    第二步初始化申请的空间 调用 构造函数 完成对象的初始化

  • delete 的原理释放单个动态空间):

    第一步清理自定义类型对象申请的资源调用对应的析构函数

    第二步释放自定义类型对象的动态空间调用 operator delete 全局函数
    delete => operator delete => free

  • new T[N] 的原理申请多个动态空间):

    第一步调用 operator new[ ] 函数开辟动态空间
    operator new[ ] 中实际也是调用了 operator new 全局函数
    一次性完成了N个对象空间的申请

    第二步在申请的空间上执行N次构造函数完成N个对象的初始化

  • delete[ ] 的原理释放多个动态空间):

    第一步在释放的对象空间上执行N次析构函数完成N个对象中资源的清理

    第二步调用 operator delete[ ] 释放空间
    ​​​​​​​在 operator delete[ ] 中实际也是调用了 operator delete 全局函数

图示:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

四 . 定位new表达式(placement-new)(了解)

  • 定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象
    通过对象指针能够显式调用构造函数进行初始化
  • 使用格式
    调用默认构造函数new (place_address) type
    调用有参构造函数new (place_address) type (initializer-list)
    place_address必须是一个指针initializer-list类型的初始化列表
  • 使用场景
    定位new表达式在实际中一般是配合内存池进行使用
    因为内存池分配出的内存没有被初始化所以如果是自定义类型的对象
    则需要使用new的定位表达式进行显式调用构造函数来进行初始化
图示:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

本篇博客相关代码

Test.cpp文件 — C++文件:

#define _CRT_SECURE_NO_WARNINGS 1#include #include using namespace std;全局变量(链接属性:其它文件也可用)://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";///*//* 这里没有使用数组符号[]进行拷贝,//* 所以指针是直接指向常量区中“abcd”的位置的//*///////malloc开辟动态空间://int* ptr1 = (int*)malloc(sizeof(int) * 4);////calloc开辟动态空间://int* ptr2 = (int*)calloc(4, sizeof(int));////realloc开辟动态空间://int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);//}A类://class A//{//public: //公有成员函数://////构造函数(全缺省)://A(int a = 0)//: _a(a)//{////调用则打印://cout << "A():" << this << endl;//}//////析构函数://~A()//{////调用则打印://cout << "~A():" << this < 构造函数 -> 产生临时对象//* 2、临时对象 -> 拷贝构造函数 -> (A类)匿名对象 // -> 找到A类中的构造函数//*///////释放自定义类型对象空间://delete p6;//delete[] p10;///*//* delete对于自定义类型://* 先调用析构函数销毁对象清理资源,//* 再调用释放动态空间,//*/////return 0;//}//int main()//{//try//{//char* p1 = new char[0x7fffffff];///*//* 十六进制:0x7fffffff -- 接近2G//*//* 当new开辟的空间过大时可能会开辟失败,//* 开辟失败则会抛出异常//*(C语言开辟失败会返回空指针)//*/////cout << (void*)p1 << endl;///*//* char* 在被cout识别时后先被识别为char,//* 而不是我们想要打印的指针(地址),//* 所以要强转为void*类型//*///}//catch (const exception& e)//{////try……catch……捕获异常://cout << e.what() << endl;//}//////return 0;//}栈类://class Stack//{//public: //公有成员函数://////构造函数://Stack(int capacity = 4)//{////调用了构造函数则打印://cout << "Stack(int capacity = 4)" << endl;//////使用new开辟栈容量大小的空间://_a = new int[capacity];////_top = 0; //栈顶值默认为0//_capacity = capacity; //设置栈容量//}//////析构函数://~Stack()//{////调用了析构函数则打印://cout << "~Stack()" < 封装malloc/free -> 处理失败抛异常问题)//*///////上面都是new开辟单个空间,那如果开辟多个空间呢://Stack* p3 = new Stack[10]; //开辟多个空间///*//* new 实现的两步: 1、开辟对象空间2、调用构造函数初始化//* //*1、开辟对象空间//* new 开辟单个空间和开辟多个空间同样都会调用operator new,//* 开辟多个空间实际只会调用一次operator new,//* 一次性就开辟多个连续的空间(这里是10个连续的空间)//*(operator new[] -> operator new -> malloc)//* //* 2、调用构造函数初始化//* 调用10次Stack构造函数进行初始化//*/////delete[] p3; //释放new开辟的连续空间///*//* 1、先调用10次析构函数;//* 2、释放空间://* operator delete[] -> operator delete -> free//* //*补充://* 前面我们用new开辟了10个连续的空间,//* 按理来说应该是120个字节(这里一个栈对象12个字节),//* 但实际开辟了124个字节,多的4个字节存储着开辟的空间个数,//* 这里存储就是10,这样第一步中就可以知道要调多少次析构函数,//* 第二步中也可以知道释放时要释放多少个连续的空间,//* 所以我们使用delete释放连续空间时“delete[]"中的[],//* 我们不需要显式写出要释放多少个连续空间,//* 因为在用new开辟连续空间的时候就已经存储好了该值//*(对于构造函数中申请了资源的自定义类型来说)//* //* 所以 new 要和 delete 配对使用,//*new[] 要和 delete[] 配对使用,//* malloc 要和 free 配对使用//*/////return 0;//}//A类:class A{public: //公有成员函数://构造函数(全缺省):A(int a = 0): _a(a){//调用则打印:cout << "A():" << this << endl;}//析构函数:~A(){//调用则打印:cout << "~A():" << this <A(1); //不能像437行那样显式调用构造函数,//但可以通过 定位new 显式调用构造函数:new(p1)A(1);/** 定位new是在已分配的原始内存空间中调用构造函数* 来初始化一个对象* *格式:* 默认构造:new(对象指针)对象类名* 有参构造:new(对象指针)对象类名(初始化值)*///虽然构造函数不能显式调用,但析构函数是可以的:p1->~A();//析构函数可以显式调用也可以自动调用//释放空间:operator delete(p1);/**某种程度上来说:* *A* p1 = (A*)operator new(sizeof(A));*+*new(p1)A(1);* * operator new开辟空间配合定位new,可以实现new的功能*(operator new开辟空间,定位new再显式调用构造函数初始化)*//**某种程度上来说:* *p1->~A();*+*operator delete(p1);* * p1->~A();显式调用析构函数配合operator delete释放空间,* 可以实现delete的功能*//** 虽然可以模拟实现new和delete,* 但一般也不会这么操作** 因为new有两步操作,* 1、operator new -> malloc(去堆中申请空间)* 2、调用 构造函数* 所以如果频繁使用new申请小对象的话,一直去找堆的话,* 效率可能会比较低。* * 这时就需要使用到 内存池,* 把堆的内存较大地申请到内存池中,* 这时当需要申请内存时就不到堆中申请了,* 而是到内存池中申请,不够了再到堆中申请,* 这时就不用一直到堆中申请,提高效率* 内存池只开了空间,没有初始化,也不能初始化,* 因为数据可能会是私有的(池化技术)* * 假设我们有一个内存池,在内存池中申请对象空间,* 这时的初始化工作就可以交给 定位new ,* 通过 定位new 显式调用构造函数来初始化对象,* 这时要释放空间还给内存池的话,* 就需要显式调用析构函数来释放空间*/return 0;}//C++常见面试题:指针和引用的区别、malloc和new的区别