目录
- C/C++的内存分布
- new/delete
- new
- 内置类型使用new
- 自定义类型使用new
- new失败
- delete
- 内置类型使用delete
- 自定义类型使用delete
- new和delete的实现原理
- new[] 和delete[]的补充知识
- 定位new(了解)
- 常见面试题
C/C++的内存分布
频繁的new/delete堆容易产生内存碎片,栈上则没有这个问题(因为是连续分配)
栈是先进后出,先进的在内存地址大的
堆无法静态分配,只能动态分配
栈可以通过函数_alloca进行动态分配,不过注意,所分配空间不能通过free或delete进行释放
不同的数据有不同的存储需求,内存的个区间划分为了满足这些不同的需求
1、临时用(局部变量、数组等) - > 栈
2、动态使用(数据结构、算法中需要动态开辟一些空间) - > 堆
3、整个程序期间都要使用(全局变量,静态变量) - > 静态区/数据段
4、只读数据(常量、可执行代码) - > 代码段/常量区
可执行代码是指二进制代码(电脑可以看懂的那个)
咱写的那个代码存在磁盘上(存在文件中)
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.代码段(常量区)
1、globalVar在哪里?____ 2、staticGlobalVar在哪里?____
3、staticVar在哪里?____ 4、localVar在哪里?____
4、num1 在哪里?____
5、char2在哪里?____ 6、*char2在哪里?___
7、pChar3在哪里?____ 8、*pChar3在哪里?____
9、ptr1在哪里?____ 10*ptr1在哪里?____
填空题:
11、sizeof(num1) = ____;
12、sizeof(char2) = ____; 13、strlen(char2) = ____;
14、sizeof(pChar3) = ____; 15、strlen(pChar3) = ____;
16、sizeof(ptr1) = ____;
1、C globalVar是全局变量 - 在静态区
2、C staticGlobalVar是静态全局变量 - 在静态区
3、C staticVar是静态变量 - 在静态区
4、A num1是数组名指的是首元素地址 - 在栈上
5、A char2是数组名 - 在栈上
6、A *char2指向的是根据常量字符串创建的数组 - 在栈上
7、A pchar3是指针 - 在栈上
8、D *pchar3是值得常量字符串 - 在常量区
9、A ptr1是指针 - 在栈上
10、B *ptr1是指针指向的那块动态开辟的空间 - 在堆上
11、40
12、5
13、4 (strlen不算\0)
14、4/8
15、4
16、4/8
补充
1、下面三者的相同点与不同点
相同的:生命周期都是全局
不同点:作用域不同。globalVar是所有文件都可以使用;staticGlobalVar是只能当前文件使用;staticVar是只能在Test函数里面使用
2、const
(1)const char* pChar3 = "abcd"
const修饰的是pChar3所指向的内容不能改变
(2)char* const pChar3 = "abcd"
这个const才是修饰pChar3这个指针的内容不能被修改
(3)const int n = 10
n是常变量
3、字面量
10、‘x’、“111111111”、1.1
4、calloc:开辟空间并初始化为0
new/delete
new
内置类型使用new
使用方法:new+类型
new对于内置类型不会初始化, 初始化(一个数据用(),多个数据用{})
//申请一个int空间
int* p1 = new int;
int* p2 = new int[3];
//初始化
int* p3 = new int(3);
int* p41 = new int[3]{1, 2, 3};
int* p4 = new int[10]{1, 2, 3};//不用10个都初始化,前三个是123,后七个是0
//释放
delete p1;
delete[] p2;
delete p3;
delete[] p41;
delete[] p4;
自定义类型使用new
malloc不方便解决动态申请的自定义类型对象的初始化问题
new:1、开空间; 2、调用构造函数
new失败
new失败了会抛异常,异常必须被捕获(继承和多态讲)
出错了之后会直接到捕获的地方(往catch的地方跳)
delete
内置类型使用delete
使用方法:new的就用delete;new[] 的就用delete[]
自定义类型使用delete
delete:析构函数 + 释放空间
new和delete的实现原理
new:operater new + 调用构造函数
delete:调用析构函数 + operater delete
operater new:封装了malloc,加了个出错时的报错机制
operater delete:封装了free,加了个出错时的报错机制
operater new和operater delete是函数,new和delete是操作符
new[] 和delete[]的补充知识
class Stack
{
public:
Stack(int capacity = 4)
:_a(nullptr)
,_top(0)
,_capacity(capacity)
{
_a = new int[capacity];
}
~Stack()
{
delete[] _a;
_a = nullptr;
_top = 0;
_capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack* p3 = new Stack[10];
delete[] p3;
return 0;
}
1、new[]的底层
因为p3指向的是个数据,空间上是连续的,因此new只调用了一次
2、new Stack[10]这个开辟的大小本应该120,但实际上是124
(1)new[]偷偷在最前面开了4字节放-- 一共开的对象数目
(2)delete[] 就会先将指针向前移动4字节,然后根据其内容知道是调用10次析构函数
(3)正是由于(2)条,delete时就不会将指针向前移动四个字节,因此报错
(4)但有时即使像(3),但也不会报错
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
A(const A& i)
{
_a = i._a;
cout << "A(const A& i)" << endl;
}
/*~A()
{
cout << "~A()" << endl;
}*/
private:
int _a;
};
int main()
{
A* p2 = new A[10];
delete p2;
return 0;
}
原因:
因为A没有析构函数,所以系统任务A的对象没有什么可析构的,调不调析构函数都可以,因此在new[]时会进行优化,直接不创建最前面的四个字节来显示一个创建了几个A对象
因此在delete的时候本来就不需要向前移动四个字节,所以不会报错
定位new(了解)
在已分配的原始内存空间中调用构造函数初始化一个对象
new(place_address)type或者new(place_address)type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
int main()
{
A* p3 = (A*)operator new(sizeof(A));
//定位new去调用构造函数,构造函数不能显示调用
new(p3)A(1);
//析构函数可以显示调用
p3->~A();
return 0;
}
定位new去调用构造函数,构造函数不能显示调用
析构函数可以显示调用
虽然上面的两行相当于new,一般情况下我们会用new(方便)
特殊情况我们也会用那两行
频繁申请小对象是,需要频繁的去堆上开辟空间 – 池化技术,内存池
内存池
1、内存池申请对象空间
2、定位new显示调用构造函数
3、释放对象:显示调用析构函数,将内存还回内存池
常见面试题
用法+原理
malloc/free和new/delete的区别?
malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地
方是:
- malloc和free是函数,new和delete是操作符
- malloc申请的空间不会初始化,new可以初始化
- malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,
如果是多个对象,[]中指定对象个数即可 - malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
- malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需
要捕获异常 - 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new
在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成
空间中资源的清理