- C/C++内存分布
从上往下:
- 内核空间
- 栈。存储局部变量,往低地址增长。空间比较小,但是申请释放内存速度极快
- 内存映射段。用于装载一个共享的动态内存库,用户可使用系统接口创建共享共享内存,做进程间通信。
- 堆。动态开辟的空间,往高地址增长。空间非常大,申请释放速度较慢,且需要手动释放
- 数据段。存储全局变量和静态变量
- 代码段。存储函数二进制指令,常量
- C语言中动态内存管理方式
- 申请
使用 malloc 函数申请空间,返回一个void指针,如果申请失败则返回NULL - 释放
使用 free 释放空间,参数可以传NULL - 拓展
calloc
函数原型:void* calloc(unsigned int num, unsigned int size);
函数功能:return malloc(num * size);
realloc
函数原型:extern void *realloc(void *mem_address, unsigned int newsize);
函数功能:给mem_address申请一块newsize大小的空间,将原空间的内容按字节拷贝到新空间上,并返回新的地址。申请失败返回NULL
特殊情况:若newsize为0,则返回NULL并free原空间
- C++中动态内存管理
申请和释放单个元素的空间:new/delete
new 类型(初始化列表)
delete 地址
申请和释放连续的空间:new[]/delete[]
new 类型[对象的个数]
delete[] 地址
- 与malloc/free的区别
- malloc和free是函数,new和delete是操作符
- malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可
- malloc的返回值为void*, 在使用时必须强转,new不需要
- malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
- 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理
使用方法举例:
class date {
public:
date(int year = 1970, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day) { }
private:
int _year;
int _month;
int _day;
};
int main() {
date* d = new date(1999, 6, 1);
date* pd = new date[5];
delete d;
delete[] pd;
return 0;
}
需要注意的是,代码中的new data[5]
在申请空间之后会调用5次构造函数,所以若是data没有默认构造的话编译器将会报错。
- new和delete的实现原理
-
内置类型
这种情况下new和malloc,delete和free基本类似 -
自定义类型
new的原理- 调用operator new函数申请空间
- 在申请的空间上执行构造函数,完成对象的构造
delete的原理
- 在空间上执行析构函数,完成对象中资源的清理工作
- 调用operator delete函数释放对象的空间
new T[num]的原理
- 调用operator new[]函数,在operator new[]中实际调用operator new函数完成num个对象空间的申请
- 在申请的空间上执行N次构造函数
delete[]的原理
- 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
- 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
总结:new和delete本质上是对malloc和free的封装,申请释放空间的功能依旧是由malloc和free完成的,只不过增加了调用构造函数、析构函数、抛异常等功能
- 定位new表达式(placement-new)
在已分配的原始内存空间中调用构造函数初始化一个对象
使用格式:
new(地址) 类型名
或new(地址) 类型名(初始化参数列表)
- 一些应用场景
- 设计一个只能在堆上创建对象的类
只能在堆上创建对象,意味着不能在栈上创建对象(废话),也就是说在栈上创建对象的方法要被禁用。
而类的6个默认成员函数中,能在栈上创建对象的有构造函数和拷贝构造函数,只要将它们禁用就行了。
需要注意的是,使用new创建对象也是需要调用构造函数,因此这里只需要禁用拷贝构造,而构造函数则只需要封装一下。
下面举个例子演示一下class HeapOnly { public: //声明为静态类型才可以直接通过类名调用 static HeapOnly* CreatObject() { return new HeapOnly(1); } private: //构造函数不能被禁用 HeapOnly(int num) :x(num){}; //拷贝构造随便禁 HeapOnly(const HeapOnly&) = delete; int x; };
- 设计一个只能在栈上创建对象的类
同上面的情况类似,需要将operator new系列的函数禁用class StackOnly { public: StackOnly() {}; private: //禁止使用new创建单个对象 void* operator new(size_t) = delete; //禁止使用new连续创建多个对象 void* operator new[](size_t) = delete; };