索引
c++中程序内存区域的划分
栈:又叫堆栈,非静态局部变量/函数参数/返回值等等,栈是向下增长的
堆:用于程序运行时动态内存分配,堆是可以上增长的
数据段:存储全局数据和静态数据。
代码段:可执行的代码/只读常量。
问
int main()
{
char arr[] = "abcd";
const char* arr2 = "abcd";
return 0;
}
arr在哪里?*arr在哪里?
arr2在哪里?*arr2在哪?
arr是属于常量字符串拷贝过去然后初始化他,实际上还是在栈上,所以不管是arr还是 *arr
都是在栈上
但是arr2存的是常量字符串的地址,其本身是在栈上的,但是*arr2
指向的是常量字符串
malloc/calloc/realloc区别
new/delete
C语言中的动态内存管理方式在c++中依然可以使用,但是由于c++中引入了类和对象,此时c++又产生了自己的内存管理方式:通过new和delete操作符进行动态内存管理
int main()
{
int* p1 = new int;
int* p2 = new int[10];//new10个int的对象
int* p3 = new int(10);//new一个对象并且初始化为10
int* p4 = new int[10]{ 10,1,2,3 };//new10个int的对象并且初始化成括号中的内容
//申请完空间后一定得释放空间,如果不匹配那么可能会报错
delete p1;
delete[]p2;
delete p3;
delete[]p4;
return 0;
}
new与malloc对于内置类型的空间申请都是一样的,只是对于自定义类型的空间申请还是有所不同的。
假设目前有一个最简单的类
class Date
{
public:
Date(int year = 1, int month = 1,int day = 1)
:_year(year)
,_month(month)
{
_day = day;
//为了区分在new其对象时调用自动调用了拷贝构造
cout << "Date(int year = 1, int month = 1,int day = 1)" << endl;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
~Date()
{
//为了区分在new其对象时调用自动调用了析构
cout << "~Date()" << endl;
}
private:
int _year;
int _month;
int _day;
};
下面同时用new和malloc同时为这个类申请一个对象
int main()
{
Date*d1 = (Date*)malloc(sizeof(Date));
assert(d1);
Date* d2 = new Date;// 开空间+调用构造函数初始化
d1->Print();
d2->Print();
free(d1);
delete d2;//调用析构函数清理资源 + 释放空间
return 0;
}
所以在申请自定义类型空间时,new在开完空间后会自动调用构造函数,delete会调用完析构函数后再释放空间
异
在malloc开空间失败的会返回空指针,所以每次在调用完malloc后会检查一下此时指针是否为空指针,但是new开空间失败会直接抛异常
operator new与operator delete函数
上述中了解到new和delete是操作符,其底层原理靠的就是operator new与operator delete函数,
注
operator new是一个函数,并不是运算符重载
operatot new
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
void* p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
// report no memory
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
上述是operator new的源码,我们可以了解到该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,
尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
//operator delete源码
void operator delete(void* pUserData)
{
_CrtMemBlockHeader* pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK); /* block other threads */
__TRY
/* get a pointer to memory block header */
pHead = pHdr(pUserData);
/* verify block type */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg(pUserData, pHead->nBlockUse);
__FINALLY
_munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY
return;
}
可以了解到operator底层也是通过_free_dbg实现的,再来看看free底层的源码
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
所以说operator delete 本质底层上调用的还是free
综上:
new = operator new+构造函数
delete = 析构函数+operator delete
new X[N]原理:
调用operator new[]函数,在operator new函数中完成对N个对象空间的申请,然后在申请的空间上执行N次构造函数
delete []原理:
在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理。
定位new与池化技术
先打个比方,假设现在堆区是妈妈的钱包,一片空间(你自己开辟好的)相当于是你自己的钱包,然后向堆区中申请空间相当于我们每天一日三餐的饭钱,如果我们每一餐都问老妈要,ok,老妈最后肯定都会给,但是次数太多,频繁的话,不说老妈烦不烦,单单是效率就非常底,一个程序多次向堆区中申请与释放空间也是这样,直接想堆区中申请释放空间的话效率非常底,所以便有了池化技术
由于现在所学有限,池化技术以后再更新
什么是定位new?
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义
类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化
使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
因为如果我们是new一个自定义对象的话,且不说new是直接向堆区中申请空间,而且其会自动初始化,但是如果我们是malloc一个自定义类型的话,我们后面就无法初始化了,因为构造函数是不能显示调用的,但是如果我们如果用定位new(定位new使用的时候是向内存池中使申请空间)的话即使空间开辟后没有及时初始化,那也没事。
eg:
Date* d1 = (Date*)operator new(sizeof(Date));
//针对一个空间,显示调用构造函数初始化。
new(d1)Date(5);//等价于 Date*d1 = new Date(5);
malloc/free和new/delete的区别
**同:**都是从堆上申请空间,并且需要用户手动释放空间
异:
1,首先malloc/free是函数,new/delete是操作符
2,new只是单纯的申请空间但是new除了申请空间之外还初始化其申请的空间,free只是释放空间,delete其先调用其所要释放空间的析构函数,然后再释放空间
3,malloc申请空间时需要手动计算空间大小并传递,new直接后面跟空间的类型即可
4,malloc的返回值是void*调用时需要进行强制转换,new只需要在后面跟上空间类型即可
5,malloc申请空间失败时会返回空,new申请空间失败时直接抛异常