初阶5"/>
【C++初阶5
前言
博主水平有限,不足之处如能斧正,感激不尽!
本期内容概览:
- 复习C语言中的动态内存管理
- malloc
- free
- C++中的内存管理
- new
- delete
- C和C++内存管理的区别
- 定位new表达式
- 内存泄漏
- 例题分析
零、数据内存分布
先来分析一下这些基本的内存分布
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";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);}
栈 | 堆 | 数据段(静态区) | 代码段(常量区)
-
globalVar在哪里?
静态区,全局变量存在静态区 -
staticGlobalVar在哪里?
静态区,全局static变量存在静态区 -
staticVar在哪里?
静态区,局部staic变量存在静态区 -
localVar在哪里?
栈,局部变量存在栈上 -
num1 在哪里?
栈,局部变量存在栈上 -
char2在哪里?
栈,char2是局部数组,常量字符串拷贝到栈上 -
*char2在哪里?
栈,*char2是局部数组的第一个元素,也在栈上 -
pChar3在哪里
栈,pChar3是局部指针变量 -
*pChar3在哪里?
常量区,pChar3是指针,指向常量区的常量字符串 -
ptr1在哪里?
栈,ptr1是局部指针变量 -
*ptr1在哪里?
堆,ptr指向的空间是动态开辟在堆上的 -
sizeof(num1) = 40
sizeof(数组名)计算整个数组的大小
-
sizeof(char2) = 5
sizeof(数组名)计算整个数组的大小,""初始化的字符数组自带’\0’
-
strlen(char2) = 4
strlen到’\0’停下,计算有效字符个数
-
sizeof(pChar3) = 4/8
指针变量的大小在32位下是4bytes,64位下是8bytes
-
strlen(pChar3) = 4
strlen到’\0’停下,计算有效字符个数
-
sizeof(ptr1) = 4
指针变量的大小在32位下是4bytes,64位下是8bytes
这波夺命连环问,让我回想起那时被C语言指针折磨的恐惧…
一、C语言中的动态内存管理
realloc和calloc在这里不赘述,就讲讲朴实的malloc和free。
malloc
是什么
C语言中用来动态开辟内存的 函数。
特性
- 仅开辟空间
- 开辟失败返回空指针
怎么用
int main()
{int* pi = (int*)malloc(sizeof(int) * 4);return 0;
}
free
是什么
C语言中用来释放 动态开辟的空间 的函数。
特性
- 仅释放空间
怎么用
int main()
{int* pi = (int*)malloc(sizeof(int) * 4);free(pi);return 0;
}
二、C++中的内存管理
C++和C语言的重要区别:对象!那么C++中的内存管理如果有改变,跟对象脱不开关系。
new 和 delete
new是什么
C++中用来动态开辟空间的 操作符。
new的特性
-
对内置类型的开辟,和就相当于malloc(语法形式不一样)
对自定义类型的开辟,开辟后会自动调用其默认构造函数
-
开辟失败抛异常(异常后面讲)
delete是什么
C++中用来释放动态开辟的空间的 操作符
delete的特性
-
对内置类型的释放, 相当于free(语法形式不一样)
对自定义类型的释放,释放前自动调用其析构函数
看了new和delete的特性,就知道他们是“针对对象升级的malloc和free”。
怎么用
- 内置类型
int main()
{//开辟内置类型int* p1 = new int;cout << *p1 << endl;//释放内置类型delete p1;//开辟内置类型并初始化int* p2 = new int(10);cout << *p2 << endl;//释放内置类型delete p2;//开辟内置类型数组int* p3 = new int[4];for(int i = 0; i<4; i++)cout << p3[i];cout << endl;//释放内置类型数组delete[] p3;//开辟内置类型数组并初始化int* p4 = new int[4]{1, 2, 3, 4};for(int i = 0; i<4; i++)cout << p4[i];cout << endl;//释放内置类型数组delete[] p4;return 0;
}:0
10
0000
1234
- 自定义类型,来看看怎么new一个对象(233
class Date
{
public:Date(int y = 2022, int m = 10, int d = 1):_y(y), _m(m), _d(d){cout << "Date(int y, int m, int d):_y(y), _m(m), _d(d)" << endl;}~Date(){cout << "~Date()" << endl;}private:int _y = 1;int _m = 1;int _d = 1;
};int main()
{Date* pd1 = new Date;delete pd1;Date* pd2 = new Date[4];delete[] pd2;return 0;
}:Date(int y, int m, int d):_y(y), _m(m), _d(d)
~Date()
Date(int y, int m, int d):_y(y), _m(m), _d(d)
Date(int y, int m, int d):_y(y), _m(m), _d(d)
Date(int y, int m, int d):_y(y), _m(m), _d(d)
Date(int y, int m, int d):_y(y), _m(m), _d(d)
~Date()
~Date()
~Date()
~Date()
- 一定记住 new 和 delete 对应,new[ ] 和 delete[ ] ]对应
- 如果new[n]/delete[n],则会调用n次 构造/析构函数
如果混着用,
- 内置类型不会报错,可以运行
- 自定义类型不会报错,运行崩溃
底层实现
new的底层调用了一个 operator new( [ ] )
operator new的实现:
/*
operator new:通过malloc申请空间
当malloc成功,直接返回
当malloc失败,尝试执行“空间不足的应对措施”,如果这个应对措施用户自己设置了,则继续申请,否则抛异常
*/
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{// try to allocate size bytesvoid *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);
}
只需要抓住这两点:
- new的底层是malloc实现的
- new失败了抛异常
Delete的底层调用了一个 operator delete/operator delete[ ]
operator delete的实现:
/* 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_FINALLYreturn;
}
/* free的实现 */
#define free(p)
_free_dbg(p, _NORMAL_BLOCK)
只需要抓住这一点:
- delete底层是free实现的
建议
C++中最好不用C的内存函数了,主要原因是自定义类型,如自定义类型容易漏掉初始化等。
三、C和C++内存管理的区别
对比一下malloc和new,free和delete就能知道。
malloc 和 new
malloc只开辟空间 | new开辟完空间对自定义类型会调用其构造函数
malloc是函数,用起来麻烦 | new是操作符,用起来方便
free 和 delete
free只释放空间 | delete释放空间前,对自定义类型会调用其析构函数,而后再释放
*free(nullptr)什么都不做
四、定位new表达式
是什么
定位到已开辟的空间,再次“new”,只不过这个new不真的开辟空间,而是可以对自定义类型调用构造
按这说法,好像没啥用啊,我开辟的时候直接调构造函数来实例化不就好了?
【池化技术】
定位new表达式多用于内存池。什么是内存池?
感性地理解一下:
你住在一个村里,日常用水需要走二里路去水井里打水。久而久之觉得来回跑又麻烦又累,于是接了很长的管道,从家里的院子直通水井,水泵只要看见院子里的蓄水池不满,就自动抽水。如此一来,就实现“用水自由”,再也不用那么麻烦了
放在内存池里看:
频繁的开辟,就要频繁的去申请、找、分配给你。不如自己搞一个内存池,它里边随时有现成的内存可以用,不用老跑去申请。
问题
但这有个问题,构造函数不能在创建对象后显式调用,也就是说,对于已经开辟好的空间,我们无法用它来实例对象了。
定位new就起到作用啦。现在再来回答为什么有定位new表达式
为什么
对已经开辟好的空间,无法显式调用构造函数来实例对象——只能通过定位new调用
特性
- 不会自动调用析构函数(要手动析构)
怎么用
class A
{
public:A(int a):_aa(a){cout << "A(int a):_aa(a)" << endl;}~A(){cout << "~A()" << endl;_aa = 0;}
private:int _aa = 0;
};int main()
{A* p = (A*)malloc(sizeof(A) * 4);//相当于拿已经开辟的空间再“new”一次A* paa = new(p)A(10);// paa->~A();return 0;
}:A(int a):_aa(a)
可以看到,并没有自动调用析构
五、内存泄漏
是什么
由于疏忽或错误,未能及时释放 不再使用的内存空间
会如何
程序会逐渐崩溃,只不过是快与慢的差别
怎么避免
- 事前预防:智能指针
- 事后查错:内存泄漏检测工具
今天的分享就到这里啦,感谢浏览。
这里是培根的blog,期待与你共同进步!
更多推荐
【C++初阶5
发布评论