【与C++的邂逅】--- C/C++内存管理

2024-08-26 17:44
文章标签 c++ 内存 管理 邂逅

本文主要是介绍【与C++的邂逅】--- C/C++内存管理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 Welcome to 9ilk's Code World

       

(๑•́ ₃ •̀๑) 个人主页:        9ilk

(๑•́ ₃ •̀๑) 文章专栏:     与C++的邂逅


C++中我们总是提到管理资源,资源可以从内存中申请,前提是我们得知道C++对内存管理的布局,本节我们就来学习这块的内容。


🏠 C/C++内存分布

我们先来看以下代码:

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);
}

对于上述代码中的各个变量它们分别在哪些区域呢?

注:对于char2他是在栈上开5个字节,然后复制在常量区的"abcd",所以如果问*char2,这里指的是首元素字符'a‘,他就是在栈上。

说明:

  • 又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。
  • 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口
    创建共享共享内存,做进程间通信。
  • 用于程序运行时动态内存分配,堆是可以上增长的。
  • 数据段--存储全局数据和静态数据。
  • 代码段--可执行的代码/只读常量。

🏠 C语言中动态内存管理方式

  • malloc
void* malloc(size_t size);

malloc这个函数功能是向内存(堆区)申请一块指定字节大小连续可用的空间,如果开辟成功,返回一个指向开辟好空间的指针;开辟失败,返回一个NULL指针。

  • free
void free(void* ptr);

free函数专门用来释放动态开辟的内存,如果参数ptr指向空间不是动态开辟,free函数的行为是未定义的;如果参数是NULL指针,则函数什么事也不做。

  • calloc
void* calloc(size_t num,size_t size);

calloc函数功能也是用来开辟指定大小的内存空间,如果开辟成功,则返回num个大小为size的连续空间的首地址且空间的每个字节都被初始化为0;如果开辟失败,则返回空指针。

  • realloc
void* realloc(void* ptr,size_t size);
//ptr是要调整的内存地址
//size是调整好新大小
//返回值为调整之后的内存起始地址

realloc函数可以调整动态开辟好的内存的大小,如果开辟成功则返回调整好之后的内存起始地址,反之开辟失败则返回空。

realloc函数调整动态内存大小的时候会有三种情况:

1. 原有空间之后有足够大空间。此时realloc函数直接在原空间后方进行扩展空间,并返回该内存空间首地址。

2.原有空间之后没有足够大空间。此时realloc函数会在堆区重新找一块合适大小的连续空间使用,把原空间数据拷贝到新空间中,并主动将原空间内存释放,返回新内存空间的起始地址。

3.扩容失败。对于第一第二种情况都没有合适空间,此时就是开辟内存失败,返回NULL。

// 1.malloc/calloc/realloc的区别是什么?
int* p2 = (int*)calloc(4, sizeof (int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
// 这里需要free(p2)吗?
free(p3 );

对于p3它可能是异地扩充,也可能是原地扩充;如果是原地扩容p2和p3指向同一块空间,free(p3)就能把这块空间释放;如果是异地扩容,此时原空间也就是p2指向空间已经被主动释放归还给操作系统。因此不需要free(p2)了。

🏠 C++中动态内存管理

📌 new/delete操作内置类型

int* p1 = new int;//默认不初始化
int* p2 = new int(10);//但是可以初始化int *p3 = new int[3];//多个元素也是默认不初始化
int* p4 = new int[3]{1,2}; //剩下的一个元素初始化为0delete p1;
delete p2;
delete[] p3;
delete[] p4;

说明:

  • new + 内置类型是动态申请一个内置类型的空间。
  • 申请某个内置类型的空间默认不初始化,但可以手动用括号进行初始化。
  • new + 内置类型[size]是申请连续size个内置类型空间。
  • 申请连续空间时默认不初始化,但也是可以对指定元素初始化,指定个数小于数组元素个数则剩下初始化为0.
  • 释放单个元素的空间时,要用delete操作符释放空间;释放连续空间时,要用delete【】释放。注意不能混着用!

new操作内置类型时,除了用法方便,其他和malloc没什么区别。new主要是给自定义类型设计的。

📌 new/delete操作自定义类型

class A
{
public:A(int a = 1):_a(a){cout << "A()" << endl;}~A(){cout << "~A()" << endl;}
private:int _a;
};int main()
{A* pa = (A*)malloc(sizeof(A));return 0;
}

如果用malloc申请一个自定义类型元素的空间,此时它的成员变量是没有进行初始化的。

A* p2 = new A;
A* p3 = new A(2);delete p2;
delete p3;

用new申请自定义类型空间时,此时会new完成的任务是申请空间分配地址+调用默认构造函数。当然你也可以显示调用其他构造。而delete完成的任务是先调用析构清理资源,再释放内存空间。

A* p4 = new A[10];//调用10次构造
delete[] p4; //调用10次析构//自定义类型数组初始化
A aa1(1);
A aa2(2);
A aa3(3);
A* p5 = new A[10]{aa1,aa2,aa3};
delete[] p5;

用new申请自定义类型的一块连续空间时,用法与内置类型一样,不同的是,申请完空间后,会对申请每个对象调用构造;同样的,释放空间需要delete[ ],此时会先调用对象个数次的析构再释放空间。如果对一部分指定初始化,剩下的会调用默认构造。

当然我们可以使用隐式类型转换使初始化变得方便

A* p6 = new A[10]{1,2,3,{6,7}};
//注前面1,2,3是单参数隐式类型转换 {6,7}是多参数隐式类型转换

总结:

1.C++中如果想申请内置类型的对象或数组,用new/delete和malloc/free没什么区别,只不过new/delete更方便些。

2.如果想申请自定义类型的对象或数组,new/delete分别是开空间+构造函数,析构函数+释放空间;malloc和free仅仅是申请空间和释放空间。

3.综上所述,C++中我们建议无论是内置类型还是自定义类型的申请和释放,尽量使用new和delete。

🏠 operator new和operator delete

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

operator new/operator delete的用法和malloc/free完全一样,都是用来申请和释放空间。

int* p1 = (int*)operator new(sizeof(int)* 10); //申请
operator delete(p1); //销毁//上面等价于
int* p2 = (int*)malloc(sizeof(int)*10);
free(p2);

实际上operator new也是通过malloc来申请空间operator delete最终也是通过free来释放空间的。

将malloc封装成operator new主要是为了失败抛异常的机制,如果malloc申请空间成功直接返回,否则执行用户提供的空间不足应对措施;如果用户提供该措施就继续申请,否则抛异常。而operator delete是为了一致性才封装free.

ListNode* n1  = new ListNode(1);
ListNode* n2  = new ListNode(2);
ListNode* n3  = new ListNode(3);n1->_next = n2;
n2->_next = n3;

在C语言实现链表这个数据结构申请节点时,我们需要判断申请是否成功,需要写if判断malloc返回值,有了new之后,如果失败了它会抛异常,不需要我们检查返回值。

  • 辨析

1.operator new是给对象申请空间的,而构造函数是给对象中成员及其某些指向其他资源的成员初始化的。

2.operator delete是用来释放对象空间的,而析构函数是用来清理某些成员指向的空间资源。

🏠 new和delete实现原理

📌 内置类型

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

📌 自定义类型

  • new的原理

1. 调用operator new函数申请空间

2. 在申请的空间上执行构造函数,完成对象构造。

  • delete的原理

1.在空间上执行析构函数,完成对象中资源的清理工作。

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

  • new T[N]的原理

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

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

  • delete[ ]的原理

1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理。

2.调用operator delete[ ]释放空间,实际在operator delete[ ]中调用operator  delete来释放空间

如果我们申请和释放空间时,错配使用,比如new出来的,用free释放会发生什么呢?


class A
{
public:A(int a = 1):_a(a){cout << "A()" << endl;}~A(){cout << "~A()" << endl;}A(const A& aa):_a(aa._a){cout << "A(const A & aa) " << endl;}private:int _a;
};int main()
{int* p3 = new int[10];free(p3);A* p2 = new A[10];free(p2);return 0;
}

对于p3,申请内置类型由于new实际是用malloc申请空间,所以用free释放不会有什么问题;但对于p2则会报错。

我们打开监视窗口查看,我们new出来A对象个数应该是10个,为什么申请的是44个字节呢?

其实这多出来的个数是存的对象个数,由于你显示写了析构,编译器会认为你这个析构是需要调用的,因此开头多开空间存了创建对象个数,导致实际空间起始地址发生偏移,而delete和free只会释放原来的起始地址导致报错,delete[ ]则会偏移到正确释放位置.

总结: 不要错配使用,一定要匹配使用,否则结果不确定.

🏠 定位new表达式

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
  • 使用格式
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表.
析构函数是能够显示调用的而构造不行,如果我们想模仿new的行为,此时我们可以使用定位new.
//模拟new
A* p1 = (A*)operator new(sizeof(A));
new(p1)A(10); //对已有空间显示调用构造//模拟delete
p1->~A();
operator delete(p1);//模拟new[]
A* p2 = (A*)operator new[](sizeof(A)*10);
for(int i = 0 ; i < 10;++i)
{new(p2+i)A(i); //用循环更好控制
}//模拟delete[]
for(int i = 0 ; i < 10;i++)
{(p2+i)->~A();
}
operator delete[](p2);
  • 使用场景

定位new表达式在实际中一般是配合内存池使用,因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,就需要使用定位new表达式进行显示调用构造函数进行初始化.

注:在未使用定位new表达式进行显示调用构造函数进行初始化之前,malloc申请的空间还不能算是一个对象,它只不过是与A对象大小相同的一块空间,因为构造函数还没有执行.

🏠 常见面试题

📌 malloc/free和new/delete的区别?

共同点:都是从堆上申请空间,并且需要用户手动释放.

不同点:

1. malloc free 函数 new delete 操作符
2. malloc 申请的空间不会初始化, new可以初始化
3. malloc 申请空间时,需要手动计算空间大小并传递, new 只需在其后跟上空间的类型即可, 如果是多个对象,[ ] 中指定对象个数即可
4. malloc 的返回值为 void*, 在使用时必须强转, new 不需要,因为 new 后跟的是空间的类型
5. malloc 申请空间失败时,返回的是NULL ,因此使用时必须判空, new 不需要,但是 new
要捕获异常
6. 申请自定义类型对象时, malloc/free 只会开辟空间,不会调用构造函数与析构函数,而 new在申请空间后会调用构造函数完成对象的初始化,delete 在释放空间前会调用析构函数完成空间中资源的清理.

本节我们认识C++中动态内存管理方式new/delete以及new[ ]/delete[ ],以及他们的实现原理,我们建议在C++中使用他们来申请和释放内存.

这篇关于【与C++的邂逅】--- C/C++内存管理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1109247

相关文章

NameNode内存生产配置

Hadoop2.x 系列,配置 NameNode 内存 NameNode 内存默认 2000m ,如果服务器内存 4G , NameNode 内存可以配置 3g 。在 hadoop-env.sh 文件中配置如下。 HADOOP_NAMENODE_OPTS=-Xmx3072m Hadoop3.x 系列,配置 Nam

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

软考系统规划与管理师考试证书含金量高吗?

2024年软考系统规划与管理师考试报名时间节点: 报名时间:2024年上半年软考将于3月中旬陆续开始报名 考试时间:上半年5月25日到28日,下半年11月9日到12日 分数线:所有科目成绩均须达到45分以上(包括45分)方可通过考试 成绩查询:可在“中国计算机技术职业资格网”上查询软考成绩 出成绩时间:预计在11月左右 证书领取时间:一般在考试成绩公布后3~4个月,各地领取时间有所不同

安全管理体系化的智慧油站开源了。

AI视频监控平台简介 AI视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒,省去繁琐重复的适配流程,实现芯片、算法、应用的全流程组合,从而大大减少企业级应用约95%的开发成本。用户只需在界面上进行简单的操作,就可以实现全视频的接入及布控。摄像头管理模块用于多种终端设备、智能设备的接入及管理。平台支持包括摄像头等终端感知设备接入,为整个平台提

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)