目录
new和malloc不能混用,delete和free也不能混用
1.前置知识
明确以下内容:
程序运行会产生一些数据,而这些数据可以是局部数据、静态数据、全局数据、常量数据、动态申请的数据等,数据需要在内存中的不同区域存储,这些不同区域为:栈区、堆区、静态区(又称数据段,存储全局数据和静态数据)、常量区(又称代码段,存储可执行代码和只读常量)等
可以看看73.【C语言】C/C++的内存区域划分文章进一步了解内存区域的划分
易错点
以下代码中ptr和*ptr各存储在什么区域?
#include <iostream>
using namespace std;
int main()
{
const char* ptr1 = "teststring";
const char ptr2[] = "teststring";
return 0;
}
ptr1和ptr2都是指针,因此都存储在栈区(在main函数的栈帧空间中),*ptr是字符串"teststring"存储在常量区(ptr1使用const修饰,常量字符串具有常性,仅只读),因为ptr1指向的是常量字符串
但ptr2指向的是字符串数组{"teststring"},该字符串数组位于栈区,"teststring"存储在常量区,但会拷贝一份给栈区,因此ptr2指向的是栈区中的"teststring"
汇编语言底层分析常量区的字符串拷贝到栈区
环境选Debug+x86,打开调试模式后转到反汇编代码,只截取有用的代码:
const char* ptr1 = "teststring";
mov dword ptr [ptr1],offset string "teststring" (04F9C00h)
const char ptr2[] = "teststring";
mov eax,dword ptr [string "teststring" (04F9C00h)]
mov dword ptr [ptr2],eax
mov ecx,dword ptr ds:[4F9C04h]
mov dword ptr [ebp-18h],ecx
mov dx,word ptr ds:[4F9C08h]
mov word ptr [ebp-14h],dx
movzx eax,byte ptr ds:[4F9C0Ah]
mov byte ptr [ebp-12h],al
注:movzx(全称move with zero-extend)的作用是将源操作数的值移动到目标操作数中,并且将目标操作数的高位部分用0填充
整个字符串的拷贝拆成4部分来分块拷贝
第一块:"test",由eax做中转寄存器
第二块:"stri",由ecx做中转寄存器
第三块:"ng",由dx做中转寄存器
第四块:隐藏的"\0",由eax做中转寄存器,只取al(eax的低8位)
详细见下图:
2.new和delete
知识回顾
之前在CC26.【C++ Cont】动态内存管理(new和delete)浅析和面向对象的方式实现链表文章中讲过new和delete的基本使用方法本文不再赘述,但讲得比较浅,本文讲深入分析new和delete未提到的点
分析new和delete的细节
new
操作符new的作用是:开辟空间并调用构造函数初始化,这是malloc所不具备的
如果对内置类型调用new,这与调用malloc开起来没有区别,但如果是自定义类型就有区别,例如以下代码:
不带参的构造函数
#include <iostream>
using namespace std;
class Myclass
{
public:
Myclass()
:_val(1)
{
cout << "Myclass()" << endl;
}
int _val;
};
int main()
{
Myclass* ret = new Myclass;
return 0;
}
下断点到return 0,查看控制台窗口:
监视窗口查看ret对象:
如果改动main函数为下方代码,求代码执行的结果
int main()
{
Myclass* ret = new Myclass[5];
return 0;
}
分析:这里的5代表对象的个数,因此会调用5次构造函数,运行结果如下:
带参的构造函数
例如以下Myclass对象:
class Myclass
{
public:
Myclass(int data)
:_val(data)
{
cout << this << ": Myclass()" << endl;
}
~Myclass()
{
cout << this <<": ~Myclass()" << endl;
}
int _val;
};
如果是一个对象可以直接在括号中传参:
Myclass* ret = new Myclass(1);
delete ret;
如果是多个对象,可以使用初始化列表
Myclass* ret = new Myclass[5]{ 1,2,3,4,5 };
delete[] ret;
或者使用匿名对象:
//较新的编译器直接优化,不会构造+拷贝构造
Myclass* ret = new Myclass[5]{ Myclass(1),Myclass(2),Myclass(3),Myclass(4),Myclass(5)};
delete[] ret;
注意传参一定要完整,因为上方代码的构造函数的参数没有缺省参数,即没有默认构造
如果是传两个参数,照葫芦画瓢,使用匿名对象传多个参就行,可以这样写:
class Myclass
{
public:
Myclass(int data,int)
:_val(data)
{
cout << this << ": Myclass()" << endl;
}
//......
int _val;
};
//---------------------------------------------
int main()
{
Myclass* ret = new Myclass[2]{Myclass(1,2),Myclass(2,3)};
delete[] ret;
return 0;
}
或者仍然使用初始化列表
int main()
{
Myclass* ret = new Myclass[2]{ {1,2},{2,3} };
delete[] ret;
return 0;
}
delete
和free不同的是,delete会调用析构函数来清理
例如以下代码:
#include <iostream>
using namespace std;
class Myclass
{
public:
~Myclass()//这里构造函数省略
{
cout << "~Myclass()" << endl;
}
int _val;
};
int main()
{
Myclass* ret = new Myclass;
delete ret;
return 0;
}
运行结果:调用了一次析构函数
同理,下方代码调用了5次析构函数
int main()
{
Myclass* ret = new Myclass[5];
delete[] ret;
return 0;
}
运行结果:
new和malloc不能混用,delete和free也不能混用
这个在CC26.【C++ Cont】动态内存管理(new和delete)浅析和面向对象的方式实现链表文章中讲过,下方将证明这一点:
例如以下不规范代码1:
int main()
{
int* ret = new int[5];
free(ret);
return 0;
}
运行时貌似没问题:
进程正常退出,退出代码为0
例如以下不规范代码2:
int main()
{
Myclass* ret = new Myclass[5];
free(ret);
return 0;
}
编译时系统报警告:
运行时报错: 自定义类型会有问题
使用new和delete操作对象时,构造函数和析构顺序
#include <iostream>
using namespace std;
class Myclass
{
public:
Myclass()
{
cout << (void*)this << ": Myclass()" << endl;
}
~Myclass()
{
cout << (void*)this <<": ~Myclass()" << endl;
}
};
int main()
{
Myclass* ret = new Myclass[5];
delete[] ret;
return 0;
}
运行结果:
按栈的规定(最先构造的对象最后析构,虽然指针指向的对象在堆上,但指针在栈上,delete操作时先找指针,因此按栈的规定)来执行构造和析构,图如下:
下篇将分析new和delete的底层实现