赞
踩
目录
4.operator new 和 operator delete 函数
7. malloc/free 和 new/delete 的区别
内核空间 | 用户代码不能读写 |
栈 | 向下增长 |
内存映射段 | 文件映射、动态库、匿名映射 |
堆 | 向上增长 |
数据段 或静态区 | 全局数据、静态数据 |
代码段 或常量区 | 可执行代码/只读常量 |
代码是存在文件里的,文件存在磁盘里(不是在栈或者代码段上)
上面的内存分布是针对虚拟进程地址空间划分的。
每个进程编译好是一个可执行程序,可执行程序会运行起来,运行起来以后就把全局变量、静态变量、常量加载到相应区域,栈和堆里的数据不需要从文件中加载进来提前开好空间。局部变量只有在程序运行起来以后main函数执行,才开始建立栈帧,main函数调用下一个函数,继续建立栈帧。堆也是,不用提前给它开空间,要用的时候申请就行。
虚拟进程地址空间不止有这几个区域,在Linux下,32位程序有2^32个地址,每个地址占一个字节,每个地址都有一个编码。平时我们说的指针其实就是地址(虚拟进程地址空间里面一个一个空间的编号)。
空指针指向一个实际的空间,编号为32个0的空间。
指针是用来存地址编码的,一个编码是32位数字,就需要4字节来存,指针大小就是4字节。
2^32 | 2^64 |
32位机器(指针4字节) | 64位机器(指针8字节) |
4G | 2^32*4G |
练习:
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);
}
1. 选择题:
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?____ staticGlobalVar在哪里?____
staticVar在哪里?____ localVar在哪里?____
num1 在哪里?____
char2在哪里?____ *char2在哪里?___
pChar3在哪里?____ *pChar3在哪里?____
ptr1在哪里?____ *ptr1在哪里?____
2. 填空题:
sizeof(num1) = ____;
sizeof(char2) = ____; strlen(char2) = ____;
sizeof(pChar3) = ____; strlen(pChar3) = ____;
sizeof(ptr1) = ____;
3. sizeof 和 strlen 区别?
malloc: void* malloc (size_t size);//size是申请空间大小,单位字节
calloc:void* calloc (size_t num , size_t size);//num是申请元素个数,size是每个元素大小,单位字节。calloc还会自动把开辟的空间,每一个字节都初始化为0。
realloc:void* realloc (void* ptr , size_t size);//ptr是起始地址,size是扩容后的总大小,单位字节
以开辟整形数组为例:使用new 和 delete,在申请的时候就可以初始化
- int main()
- {
- //内置类型
- //相比malloc和free,除了用法的区别没有其他区别
- int *p1 = new int;//用new申请空间,不初始化
- delete p1;//释放
-
- int* p2 = new int(0);//初始化成0
- delete p2;
-
- int* p3 = new int[10];//开辟10个int
- delete[] p3;
-
- int* p4 =new int [10] {1,2,3,4 };//前四个是1234,后面的都是0
- delete[]p4;
-
- return 0;
- }
new和delete对自定义类型会调构造和析构函数,而自malloc不调。
- class A
- {
- public:
- A(int a = 0)
- :_a(a)
- {
- cout << "A():" <<this << endl;
- }
-
- ~A()
- {
- cout << "~A():" << this << endl;
- }
- private:
- int _a;
- };
-
- int main()
- {
- A* p1 = new A;//自定义类调构造函数
- A* p2 = (A*)malloc(sizeof(A));//malloc不调构造
-
- delete p1;//调析构函数
- free(p2);//不调析构
-
- return 0;
- }
用类实现链表:再也不用写BuyNode了(给新节点用malloc开辟空间,还要初始化新节点),new一下自动调构造函数。
- class ListNode
- {
- public:
-
- ListNode(int val=0)
- :_next(nullptr)
- ,_val(val)
- {}
-
- //private:
- ListNode * _next;
- int _val;
-
- };
-
- int main()
- {
- ListNode* n1 = new ListNode(1);
- ListNode* n2 = new ListNode(2);
- ListNode* n3 = new ListNode(3);
- n1->_next = n2;
- return 0;
- }
new和delete要匹配,new[ ]和delete[ ]配对。new和free也不要混合用。
不匹配可能报错也可能不报错。反正就是要匹配用,就不会出问题。
例:
- class A
- {
- public:
- A(int a = 0)
- :_a(a)
- {
- cout << "A():" << this << endl;
- }
-
- ~A()
- {
- cout << "~A():" << this << endl;
- }
- private:
- int _a;
- };
-
- int main()
- {
-
- //以下所有情况都不一定,编译器不同,报不报错也不一定。这里说的报错指vs环境下会报错
-
-
- A* p3 = new A[10];
- delete p3;//会报错,且只调了一次析构函数。不写10的话,这一行不知道有多少个对象,就不知道调多少次析构函数。底层只知道总空间有多少个字节,但不知道对象大小,也就不知道对象个数。
-
- //在堆上开辟空间的时候不是开辟40字节(一个A的大小是4字节),实际申请空间是44,头四个字节(一个整形的大小)用来存对象个数,返回的时候把指针加4个字节,返回对我们来说有用的部分。释放的时候写了[]个数的话,指针又减4字节,取到对象个数来确定调多少次析构函数
-
-
- //屏蔽掉析构函数之后,就不报错了。原因:编译器自动生成析构函数,发现A只有一个内置类型,也不用释放其他的堆上的空间,所以调不调析构都无所谓,直接不多开四个字节来记录对象个数,这个个数就是用来让编译器知道调多少次析构函数的。优化后也就不报错了,释放位置不会发生偏移
-
-
-
- //————————--再看一个例子
-
- A* p4 = new A;
- delete[]p4;
- //vs报错
- //new了一个对象,delete的时候用的[],编译器以为前面存了对象个数,是new[]出来的。会让指针往前偏移,释放就出问题了
-
-
-
- return 0;
- }
检查是否开辟成功
malloc失败,返回空指针。
new失败,抛异常。不需要检查返回值。
- int main()
- {
- try
- {
- int* p1 = new int[20];
- }
- catch (exception& e)
- {
- cout << e.what() << endl;
- }
-
- return 0;
- }
底层机制:
new
1.operator new (它的底层又是malloc)
2.调用构造函数
operator new是库里面实现的全局函数,不是new的运算符重载(运算符重载参数必须有自定义类型)。void * operator new(size_t size)
delete的底层是operate delete(它底层是free)。
也可以手动调用operator new函数来动态开辟,但没必要,用new就行。
char* p2 = (char*)operator new(1);//括号里传想要空间的字节数,返回值是void*,要强转。
- class A
- {
- public:
- A(int a = 0)
- :_a(a)
- {
- cout << "A():" << this << endl;
- }
-
- ~A()
- {
- cout << "~A():" << this << endl;
- }
- private:
- int _a;
- };
-
- int main()
- {
- A* p1 = new A[10];
- delete p1;//会报错,之前讲过了
-
- //屏蔽掉析构函数之后就不报错(没有多开四个字节),有没有内存泄漏
-
- delete[] p1;
- free(p1);
- //delete和free的区别就是会不会调析构函数
- //但是这里析构函数调不调都无所谓的情况下(屏蔽掉析构函数,编译器就自己优化不调析构函数了),这俩底层是一样的,没有内存泄漏
-
-
- return 0;
- }
对内置类型:
new/delete申请或释放单个元素空间,new []/delete []申请或释放连续的空间。
申请失败new抛异常,malloc返回NULL。
对自定义类型:
new先调operator new申请空间,然后调构造函数
delete先调析构函数释放对象中资源,然后调operator delete释放对象本身
new [],先调operator new[]开空间(operator new[]实际上是在调operator new),然后调n次构造。
delete [],先调n次析构函数,再调delete []
对自定义类型,new是开空间加调用构造函数初始化。
定位new表达式是在已经分配的原始内存空间中调用构造函数初始化一个对象。
定位new,和new的不同就是不想用失败抛异常,就可以用定位new
new ( place_address ) type 或 new ( place_address) type ( initializer-list )
place_address是指针, initializer-list是类型的初始化列表
new ( place_address ) type:
- class A
- {
- public:
- A(int a = 0)
- :_a(a)
- {
- cout << "A():" << this << endl;
- }
-
- ~A()
- {
- cout << "~A():" << this << endl;
- }
- private:
- int _a;
- };
-
- int main()
- {
- A* p3 = (A*)malloc(sizeof(A));//malloc出来的不会调构造函数
- if (p3 == nullptr)
- {
- perror("malloc fail\n");
- exit(-1);
- }
-
-
- //定位new,和new的不同就是不想用失败抛异常,就可以用定位new
-
- new(p3)A(1);//对p3指向空间,调用构造函数初始化
-
- p3->~A();//析构函数可以直接显示调用
-
- free(p3);
-
- 直接用delete行不行,也可以,底层是一样的
- //delete(p3);
-
- return 0;
- }
相同点:
都是在堆上动态申请空间,且需要手动释放。
不同点:
1.malloc和free是函数,new和delete是操作符
2.malloc申请空间不会初始化,new自定义类型会初始化
3.malloc申请空间需要手动计算空间大小并传参,new 只需要写上类型名,开辟多个对象,在[ ]里指定个数即可
4.malloc的返回值类型为void* ,接收时必须强制类型转换,new不需要,new后跟的就是类型名
5. malloc失败返回NULL,使用时必须判断是否开辟成功。new不需要判空,但是要捕获异常
6.为自定义类型申请空间时,malloc和free不会调构造和析构函数。new在申请空间后会调析构函数初始化对象,delete在释放空间前会调用析构函数完成资源清理。
内存泄漏并不是指内存消失了,而是指针丢失,失去对改该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:程序正常结束,内存泄漏了最终还是会还给操作系统。但长期运行的程序如果出现内存泄漏,影响很大,会导致响应越来越慢,最终卡死。
练习:
1.C++中关于堆和栈的说法,哪个是错误的( )
A.堆的大小仅受操作系统的限制,栈的大小一般较小
B.在堆上频繁的调用new/delete容易产生内存碎片,栈没有这个问题
C.堆和栈都可以静态分配
D.堆和栈都可以动态分配
A.堆大小受限于操作系统,而栈空间一般由系统直接分配
B.频繁的申请空间和释放空间,容易造成内存碎片,甚至内存泄漏,栈区由于是自动管理,不存在此问题
C.堆无法静态分配,只能动态分配
D.栈可以通过函数_alloca进行动态分配,不过注意,所分配空间不能通过free或delete进行释放
2.ClassA *pclassa=new ClassA[5];
delete pclassa;
c++语言中,类ClassA的构造函数和析构函数的执行次数(D)
A.5,1
B.1,1
C.5,5
D.程序可能崩溃
申请对象数组,会调用构造函数5次,delete由于没有使用[],此时只会调用一次析构函数,但往往会引发程序崩溃。
3.使用 char* p = new char[100]申请一段内存,然后使用delete p释放,有什么问题?(B)
A.会有内存泄露
B.不会有内存泄露,但不建议用
C.编译就会报错,必须使用delete []p
D.编译没问题,运行会直接崩溃
答案解析:
A.对于内置类型,此时delete就相当于free,因此不会造成内存泄漏
B.正确
C.编译不会报错,建议针对数组释放使用delete[],如果是自定义类型,不使用方括号就会运行时错误
D.对于内置类型,程序不会崩溃,但不建议这样使用
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。