前言
博主水平有限,不足之处如能斧正,感激不尽!
本期内容概览:
- 复习C语言中的动态内存管理
- C++中的内存管理
- 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的特性
delete是什么
C++中用来释放动态开辟的空间的 操作符
delete的特性
看了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的实现:
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
static const std::bad_alloc nomem; _RAISE(nomem);
}
return (p);
}
只需要抓住这两点:
- new的底层是malloc实现的
- new失败了抛异常
Delete的底层调用了一个 operator delete/operator delete[ ]
operator delete的实现:
void operator delete(void *pUserData)
{
_CrtMemBlockHeader * pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK);
__TRY
pHead = pHdr(pUserData);
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg( pUserData, pHead->nBlockUse );
__FINALLY
_munlock(_HEAP_LOCK);
__END_TRY_FINALLY
return;
}
#define free(p)
_free_dbg(p, _NORMAL_BLOCK)
只需要抓住这两点:
三、C和C++内存管理的区别
对比一下malloc和new,free和delete就能知道。
malloc 和 free
malloc只开辟空间 | new开辟完空间对自定义类型会调用其构造函数
malloc是函数,用起来麻烦 | new是操作符,用起来方便
free 和 delete
free只释放空间 | delete释放空间前,对自定义类型会调用其析构函数,而后再释放
四、定位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);
A* paa = new(p)A(10);
return 0;
}
:A(int a):_aa(a)
可以看到,并没有自动调用析构
五、内存泄漏
是什么
由于疏忽或错误,未能及时释放 不再使用的内存空间
会如何
程序会逐渐崩溃,只不过是快与慢的差别
怎么避免
今天的分享就到这里啦,感谢浏览。
这里是培根的blog,期待与你共同进步!
|