为什么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);
}
选择题一:
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?____ staticGlobalVar在哪里?____
staticVar在哪里?____ localVar在哪里?____
num1 在哪里?____
请问上面填什么?(从左到右从上到下)
CCCAA
因为全局变量和satic修饰的都在静态区里
普通的变量在栈上
选择题二:
char2在哪里?____ *char2在哪里?___
pChar3在哪里?____ *pChar3在哪里?____
ptr1在哪里?____ *ptr1在哪里?____
AAADAB
对于char2 abcd字符串确实是在常量区 但是赋值给 char2的时候又拷贝了一份 char2数组名只有两种情况表示的是整个数组1sizeof(数组名)2.&数组名 其他都表示首元素地址 这里表示首元素地址
指针没有特别修饰所以在栈上。 *char2代表a a也在栈上 pchar3是一个指针 在站上 *pchar 它指向的内容在常量区(字符串在常量区)上和char2不同的是 char2根据字符串重新拷贝了一份在栈上
区域名 | 特征 |
---|---|
堆 | 动态开辟的才在堆上 |
栈 | 一般的变量 |
数据段/静态区 | 全局变量或者static修饰的变量 |
代码段/常量区 | 常量、字符串 |
注:/表示是一个东西,前者是从操作系统的角度,后者是从语言的角度
strlen 和 sizeof的区别
strlen遇到\0就结束 sizeof则要算上\0
c语言动态内存管理方式
malloc
作用:分配空间但不初始化
参数:要分配空间的总大小
malloc returns a void pointer to the allocated space, or NULL if there is insufficient memory available.
返回值:开辟成功则返回一个void的指针,就是因为返回的是void的指针所以每次用的时候都要强制类型转换。如果可用的空间不足则返回NULL空指针
calloc
作用:分配一段空间,并且初始化为零
参数:多少个元素,每个元素的大小
返回值和malloc一样
realloc
作用:重新分配空间
参数:以前指向该空间的指针,和新空间的大小
返回值:分会3种情况 成功1:异地拷贝,返回指针被改变原先的空间被释放 成功2:就地扩容 原先的指针不改变 失败则返回空指针。
例题
void Test ()
{
int* p1 = (int*) malloc(sizeof(int));
free(p1);
// 1.malloc/calloc/realloc的区别是什么?
int* p2 = (int*)calloc(4, sizeof (int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
// 这里需要free(p2)吗?
free(p3 );
}
一般不需要 因为对于成功1异地拷贝这种情况原先的空间已经被free了,对于成功2就地拷贝这种情况free(p3)就是free(p2)所以也不需要 但是如果失败 不free(p2)会造成内存泄漏,这种概率非常的低
c++管理方式
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力比如自定义类型的初始化,而且使用起来比较麻烦自己还要算分配的空间大小还要强制类型转换,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
new/delete操作内置类型
c语言内存管理函数不能随意初始化而c++的new可以,好感度+1
new 和delete 不用手动检查 失败了抛异常这一点就秒杀c语言的内存管理函数了 好感度+++++
#include <iostream>
#include<string.h>
using namespace std;
int main()
{
int* s = new int(1); // 开辟了一个空间
int* a = new int[5];// 随机在栈开辟了五个int的空间
int* b = new int[5] {1, 2, 3, 4,5}; // 开辟了五个空间并且赋初始值
delete s;
delete[]a;
delete[]b;
cout << " " << endl;
}
注意 new 和delete 要搭配使用 new 和 delete 一对 new [] 和 delete [ ] 一对
new/delete操作自定义类型
除了开空间还分别调用了构造函数和析构函数
完成了c语言的内存管理函数无法做到的一点 好感++
证明
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
// new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间,还调用了构造函数
A* p1 = (A*)malloc(sizeof(A));
A* p2 = new A(1);
free(p1);
delete p2;
A* p5 = (A*)malloc(sizeof(A) * 10);
A* p6 = new A[10];
free(p5);
delete[] p6;
return 0;
}
new 和 delete 的底层原理是什么呢?
new 和 delete 的底层原理
operator new与operator delete函数
new和delete是用户进行动态内存申请和释放的操作符,
operator new 和operator delete是系统提供的全局函数,
operator new 和 operator delete的 用法
operator new 的用法 和malloc 一样
operator delete 的用法 和 free一样
int main()
{
int* a = (int*)operator new (4*10);
operator delete (a);
return 0;
}
new在底层调用operator new全局函数来申请空间 + 构造函数初始化,delete在底层通过operator delete全局函数来释放空间+析构函数恢复初始值。
证明:
operator new 的底层是封装malloc函数的
operator new 的用法 和 malloc函数一样
- 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对
象空间的申请- 在申请的空间上执行N次构造函数
delete 同理
构造函数里面有其他的new 会死递归吗?
不会因为 new 是一个操作符 先 operater new 开辟了stack的空间 完了才调用 a的new ,a 再去开空间 a调自己的构造函数。
delete的小细节
delete 是先析构还是先operate delete
class Stack
{
public:
Stack()
{
a = new int[4];
_top = 0;
_capacity = 4;
}
~Stack()
{
free(a);
_top = _capacity = 0;
}
private:
int _top;
int* a;
int _capacity;
};
int main()
{
Stack *pa = new Stack();
delete pa;
return 0;
}
oprate new 开辟了_a _top _capacity的空间
如果先operate delete _a _top _capacity的空间不见了 则 _a开辟的内存无法释放
所以先 析构再 operate delete
为什么要new[ ] 对应 delete 【】
我们对于A类只有一个成员 int a 开辟10个类型的A 不是应该是 40吗16进制28 哪为什么汇编的结果是16进制 30 48呢?
原因在于我们delete的时候不知道要析构多少次 用额外的空间保存了这一信息
当我们把析构函数注释掉时结果又正常了 原因在于编译器生成了析构函数对于A类什么也没干 可以不用调用 也就可以不知道需要调用几次delete了所以就不需要保存额外的信息
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
int* a = new int[10];
delete a;
A* p2 = new A[10];
delete p2;
return 0;
}
delete a 会报错吗?不会 因为a是内置类型 相当于只调用了 malloc 和 free
delete p2 会报错吗?我们看一下运行结果
报错了 原因是什么呢?因为有构造函数 多申请了额外的空间而p2不是指向开头的 不能从中间释放
new [] 申请的要 delete [] 释放
定位new表达式(了解)
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化
什么是内存池
我们很多程序都是直接找内存分配空间,但是我们现在专门在内存中,开辟了一块大的空间单独给某一个程序分配,这叫内存池。
好比一个山上有一个和尚,要下山打水,他觉得太麻烦了,于是每次打水时都多打点,在山上修了一个池子。
使用格式
new (place_address) type或者new (place_address) type(initializer-list)
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
// 定位new/replacement 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;
}
内存泄漏
什么时内存泄漏
内存泄漏是已经没有用的资源忘记释放了
比如
int main()
{
int* a = new int[100 * 1024 * 1024];
int* b = new int[100 * 1024 * 1024];
int* c = new int[100 * 1024 * 1024];
return 0;
}
内存泄漏的危害
我们试着运行上面这个程序
但是当我们结束程序的时候内存又恢复正常了
这意味着我们可以不用管内存泄漏吗?不是的因为我们写的很多程序都要跑在服务器上可不敢随时停止运行,这会造成巨大的经济损失,比如去年的滴滴服务器挂了一会儿亏了好几个亿。
长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
谢谢观看
thank you