C/C++内存管理(超详解)

embedded/2025/1/20 20:01:23/

目录

1.C/C++内存分布

2.C语言动态内存管理

2.1 malloc

2.2 free

2.3 calloc

2.4 realloc

3.C++动态内存管理

3.1new/delete操作内置类型

3.2new/delete操作自定义类型

3.3operator new与operator delete函数

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


1.C/C++内存分布

内存中是如何布局的?如下图,,一般内存主要分为:代码区、常量区、静态区(全局区)、堆区、栈区这几个区域。

内存分布说明:

  1. 内核空间: 放置操作系统相关的代码和数据。(用户不能直接进行操作 ------ 可以通过调用系统提供的 api 函数)一般是给操作系统预留的内存空间,系统会进行相应的保护。
  2. 栈又叫堆栈:非静态局部变量/函数参数/返回值等等,栈是向下增长的。
  3. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。
  4. 堆用于程序运行时动态内存分配,堆是向上增长的。
  5. 数据段:存储全局数据和静态数据。(如static修饰的变量或在函数外定义的全局变量)
  6. 代码段:可执行的代码/只读常量。(如常量字符串)

2.C语言动态内存管理

2.1 malloc

函数信息:

上代码:

#include<stdio.h>
#include<stdlib.h>
int main()
{//开辟10个整型的空间:int* p = (int*)void* p = malloc(10 * sizeof(int));//1.开辟失败if(p == NULL){perror("main");return 0;}//2.开辟成功int i = 0;for(i = 0; i < 10; i++){*(p + i) = i;}//回收空间free(p);p = NULL;return 0;
}

小结:

  1. malloc向堆区申请一块连续的空间,开辟成功返回那块空间的地址,开辟失败返回NULL
  2. 因为malloc函数的返回值是void*类型,在用指针变量接收时,必须强制类型转换为对应类型
  3. 如果malloc开辟失败,可能会对空指针进行非法解引用操作,malloc开辟的空间一定要检查
  4. 使用完空间,要主动回收。因为在回收空间后,那块空间的使用权交给了操作系统,为了避免非法访问,通常会主动将指向那块空间的指针置为NULL

2.2 free

函数信息:

上代码:

#include<stdio.h>
int main()
{int a = 1;int* b = &a;free(b);//回收空间b = NULL;return 0;
}

小结:

  1. 如果参数ptr指向的空间不是动态开辟的,那么free函数的行为是标准未定义的
  2. 如果参数ptr是NULL指针,则视为无效代码,因为不能对一个不存在的地址释放空间,这样没有意义!
  3. 关于空间回收:可以是主函数结束后,所有开辟的栈帧会被还给操作系统,也可以是主动把不用的空间主动回收掉。

2.3 calloc

函数信息:

上代码:

#include<stdio.h>
#include<stdlib.h>
int main()
{//malloc与calloc进行对比//mallocint* p = (int*)malloc(40);int i = 0;for(i = 0; i < 10; i++){printf("%d\n", *(p + i));}free(p);p = NULL;//callocint* q = (int*)calloc(10, sizeof(int));for(i = 0; i < 10; i++){printf("%d\n", *(q + i));}free(q);q = NULL;return 0;
}

小结:

  1. 相比于malloc来说,calloc与malloc唯一的不同就是calloc会进行初始化,并且要指定开辟空间的单个大小及个数。

2.4 realloc

函数信息:

上代码:

#include<stdio.h>
#include<stdlib.h>
int main()
{//开辟10个整形大小int* p = (int*)calloc(10, sizeof(int));if(p == NULL){perror("main");return 0;}//如果空间不够,还需要10个整型空间,使用realloc调整空间	int* pnew = (int*)realloc(p, 20 * sizeof(int));if(pnew != NULL){p = pnew;	}//回收空间free(p);p = NULL;return 0;
}

小结:

realloc开辟空间原理:

  1. realloc在调整空间时,发现后面有足够的空间能调整,则直接紧挨着原空间之后进行调整,并且返回原空间的地址;
  2. realloc在调整空间时,发现后面没有足够的空间能调整,则会找一块能容纳原空间和需要增加的增加空间的新空间,并把原空间的内容全部拷贝到新空间,同时把原空间主动释放掉,最后在返回新空间的地址。

3.C++动态内存管理

3.1new/delete操作内置类型

关于new和delete我们通过代码来介绍:

int main()
{// 动态申请一个int类型的空间int* ptr4 = new int;// 动态申请一个int类型的空间并初始化为10int* ptr5 = new int(10);// 动态申请10个int类型的空间int* ptr6 = new int[10];delete ptr4;delete ptr5;delete[] ptr6;return 0;
}
讲解
申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,将new[]和delete[]匹配起来使用。如果要进行初始化的话,对于内置类型单个变量使用(初始值),如果是数组则在[]后加上{初始值1,初始值2,初始值3......};

3.2new/delete操作自定义类型

上代码:

class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;}~A(){cout << "~A():" << this << endl;}
private:int _a;
};
int main()
{A* p1 = (A*)malloc(sizeof(A));A* p2 = new A(1);free(p1);delete p2;//这里将delete和C语言中的free混用对于自定义类型来说看不出区别//但对于内置类型有很大概率会出bug,不是好的代码习惯!int* p3 = (int*)malloc(sizeof(int)); int* p4 = new int;free(p3);delete p4;//这样才是正确回收空间操作,malloc匹配free,new匹配deleteA* p5 = (A*)malloc(sizeof(A) * 10);A* p6 = new A[10];//这里可以对A数组进行初始化,可以直接给值,也可以用匿名对象构造初始化//A* p7 = new A[10]{ 1,2,3,4,5,6,7,8,9,10 };//A* p8 = new A[10]{ A(1),A(2),A(3),A(4),A(5),A(6),A(7),A(8),A(9),A(10) };free(p5);delete[] p6;return 0;
}

注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会,这是C++动态操作与C语言动态操作的区别之一,为什么会调用析构和构造函数,请继续往下看:

先来介绍一下operator new和operator delete:

3.3operator newoperator delete函数

先弄清几个点:

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

纸上得来终觉浅,我们扒开两个函数的底层实现看看:

/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,
尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
*/
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
// report no memory
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
/*
operator delete: 该函数最终是通过free来释放空间的
*/
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)

小结:

  1. operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。如何查看异常呢,使用try+catch来捕捉:
    #include"iostream"
    using namespace std;
    class A
    {
    public:A(int year=2025,int month=1,int day=19){cout << year << "-" << month << "-" << day << endl;this->_year = year;this->_month = month;this->_day = day;}~A(){cout << "Destroy" << endl;_year = _month = _day = 0;}
    private:int _year;int _month;int _day;
    };int main()
    {try{A* tmp = new A[10]{ A(2025,1,10),A(2025,1,11) };//先operator new->malloc再构造delete[] tmp;//先析构再operator free->free}//malloc失败返回空指针 new失败抛异常捕捉catch (const exception& e){cout << e.what() << endl;}return 0;
    }
  2. operator delete 最终是通过free来释放空间的
  3. new操作符首先调用operator new全局函数通过底层malloc开辟一块空间,再调用构造函数对自定义类型初始化。
  4. delete操作符首先调用析构函数销毁自定义类型占用的空间,再调用operator delete全局函数通过底层free释放空间。

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

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象
使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
使用场景:
定位new表达式在实际中一般是 配合内存池使用 。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。
int main()
{
// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
A* p1 = (A*)malloc(sizeof(A));
new(p1)A; // 如果A类的构造函数有参数时,此处需要传参
p1->~A();
free(p1);
A* p2 = (A*)operator new(sizeof(A));
new(p2)A(10);
p2->~A();
operator delete(p2);
return 0;
}
由于内容太过超前,作者暂且介绍这么多~
- - - - - - ——————————本文结束—————————— - - - - - -

http://www.ppmy.cn/embedded/155570.html

相关文章

[Python学习日记-78] 基于 TCP 的 socket 开发项目 —— 模拟 SSH 远程执行命令

[Python学习日记-78] 基于 TCP 的 socket 开发项目 —— 模拟 SSH 远程执行命令 简介 项目分析 如何执行系统命令并拿到结果 代码实现 简介 在Python学习日记-77中我们介绍了 socket 基于 TCP 和基于 UDP 的套接字&#xff0c;还实现了服务器端和客户端的通信&#xff0c;本…

统计学习算法——支持向量机的基本概念

内容来自B站Up主&#xff1a;FunInCode https://www.bilibili.com/video/BV16T4y1y7qj、风中摇曳的小萝卜https://www.bilibili.com/video/BV1vv4y1g721&#xff0c;仅为个人学习所用。 支持向量机中的复杂的数学推导本文不涉及&#xff0c;仅为概念理解。 超平面 若数据在一…

MySQL 数据操作语言 (DML)

MySQL 数据操作语言 (DML) 详细介绍及代码示例 一、引言 MySQL 是一种广泛使用的开源关系型数据库管理系统。数据操作语言 (DML) 是 SQL 的一个子集&#xff0c;主要用于对数据库中的数据进行插入、更新和删除操作。本文将详细介绍 MySQL 中的 DML 语句&#xff0c;并提供相应…

图论1-问题 B: 算法7-4,7-5:图的遍历——深度优先搜索

题目描述 深度优先搜索遍历类似于树的先根遍历&#xff0c;是树的先根遍历的推广。其过程为&#xff1a;假设初始状态是图中所有顶点未曾被访问&#xff0c;则深度优先搜索可以从图中的某个顶点v出发&#xff0c;访问此顶点&#xff0c;然后依次从v的未被访问的邻接点出发深度优…

SpringMVC 实战指南:打造高效 Web 应用的秘籍

第一章&#xff1a;三层架构和MVC 三层架构&#xff1a; 开发服务器端&#xff0c;一般基于两种形式&#xff0c;一种 C/S 架构程序&#xff0c;一种 B/S 架构程序使用 Java 语言基本上都是开发 B/S 架构的程序&#xff0c;B/S 架构又分成了三层架构三层架构&#xff1a; 表现…

thinkphp:实现压缩文件上传、解压、文件更名、压缩包删除功能,增加trycatch

代码 public function upload_firstsure() {try {// 检查是否有文件上传if (!isset($_FILES[file]) || !is_uploaded_file($_FILES[file][tmp_name])) {throw new \Exception(未接收到文件或文件上传失败);}// 获取上传的文件$uploaded_file $_FILES[file][tmp_name];$file_t…

基于Java+Sql Server实现的(GUI)学籍管理系统

基于Java实现的学籍管理系统 1.运行环境 1.1服务器要求 sql server 2008 及以上 1.2客户端要求 装有jvm 并与服务器在同一内网内&#xff0c;可ping通即可 2.功能说明 简化了数据库的使用者&#xff0c;即没有根据用户名自动切换布局的功能&#xff0c;目标使用者即为管…

【JVM】总结篇之GC性能优化案例

文章目录 性能优化案例1&#xff1a;调整堆大小提高服务的吞吐量初始配置优化配置 性能优化案例2&#xff1a;JVM优化之JIT优化即时编译对代码的优化逃逸分析编译器优化栈上分配同步省略标量替换 性能优化案例3&#xff1a;合理配置堆内存推荐配置如何计算老年代存活对象结论你…