C/C++动态内存管理
C/C++动态内存管理
C/C++程序内存分布
为了更深刻的理解内存分布,可以根据下面的代码来对应上面的数据分布
int gloVal = 10; //全局变量数据段
static int staticGloVal = 20; //静态变量数据段
int main(){
static int staticVal = 30; //静态变量数据段
int a = 40; //栈区
int arr[] = {1, 2, 3};
char str[] = "abcd"; //“abcd”只读常量代码段
char *pStr = "abcd";
int *pStr1 = (int *)malloc(sizeof(int)); //动态申请的内存位于堆上
int *pStr2 = (int *)calloc(4, sizeof(int));
int *pStr3 = (int *)realloc(pStr2, sizeof(int)*3);
free(pStr1);
free(pStr3);
}
栈上存储非静态局部变量,函数参数,返回值,向下增长
内存映射,装载共享的动态内存库,可以使用系统调用接口创建共享内存
堆用于程序运行时的动态内存分配,向上增长
数据段存储全局变量和静态数据
代码段可执行的代码和只读常量
C语言动态内存管理方式
malloc
函数原型
void *malloc(size_t size);
- malloc函数向内存申请一块连续可用的大小为size的空间,申请成功返回指向这块空间的指针。
- 申请失败会返回一个NULL指针,所以在使用malloc时一定要做返回值类型检查
- 函数的返回值类型是void*,malloc函数并不知道开辟空间的类型,使用时由用户自行决定
C语言提供了专门的函数用来回收动态内存,函数原型
void free(void *ptr);
- 参数ptr指向的内存不是动态开辟的,free函数的行为是未定义的
- 参数ptr是NULL,函数什么事情也不做
calloc
C语言还提供了一个函数calloc用来动态内存分配。
void *calloc(size_t nmemb, size_t size);
- calloc为nmemb个大小为size的元素开辟一块空间,并把空间的每个字节全部初始化为0
- 与函数malloc的区别只在于calloc在返回地址之前会把每个字节初始化为0
realloc
在申请完空间之后我们可能会觉得申请的空间太小了或者申请的空间过大,这时候需要对内存进行一定的调整,realloc函数可以对动态开辟的内存进行调整
void *realloc(void *ptr, size_t size);
- ptr是要调整的内存地址,size是调整之后的大小
- 函数返回值为调整之后的内存的起始位置
- realloc在调整原内存空间大小的基础上会将原来内存中的数据移动到新的空间
- realloc在调整空间时有两种情况
- 情况一:扩展内存时在原有内存之后直接追加空间,原来数据不变
- 情况二:原有空间后没有足够多的空间,会在堆空间上重新找一个大小合适的连续空间来使用,并把原来空间的数据拷贝到新空间,函数返回新空间的地址
C++动态内存管理方式
C语言的内存管理方式在C++中可以继续使用,但是在有些地方使用起来不是很方便,所以C++提出了自己的内存管理方式:通过**new**和**delete**操作符进行动态内存管理
new/delete操作内置类型
void test(){
int *p1 = new int; //申请一个整形空间
int *p2 = new int(1); //申请一个整形空间并初始化为1
int *p3 = new int[2]; //申请两个整形空间
delete p1;
delete p2;
delete p3;
}
new/delete操作自定义类型
class Date {
public:
Date(int year = 2018, int month = 12, int day = 10)
: _year(year)
, _month(month)
, _day(day)
{
std::cout << "Date" << this << std::endl;
}
~Date() {
std::cout << "~Date" << this << std::endl;
}
private:
int _year;
int _month;
int _day;
};
int main(){
Date* d1 = new Date;
Date* d2 = new Date[5];
delete d1;
delete[] d2;
}
可以看到,在申请和释放自定义类型的空间时,new和delete会调用构造和析构函数,而malloc和free不会,只是单纯的申请空间。
operator new和operator delete
new和delete是用户进行动态内存申请和释放的操作符,operator new和operator delete是系统提供的全局函数,new和delete会在底层调用这两个函数
- operator new会通过malloc来申请空间,当malloc申请空间成功时返回地址,申请失败时会执行应对措施,如果用户设置则继续申请,否则抛异常,所以使用new申请的空间可以不用判断是否合法,当申请空间成功会调用构造函数
- operator delete调用之前会先执行类的析构函数,之后通过free来释放空间,
- operator new和operator delete用户也可以自己实现成全局函数和类的成员函数,优先调用类的成员函数,再是全局函数
new typename[]和delete[]
- 使用new int[N]来申请内存空间,会先调用operator new[]函数,在operator new[]中实际调用operator new函数完成对N个对象空间的申请,之后在申请的N个空间上调用构造函数
- 调用delete []之后,先在要释放的空间上执行N次析构函数,完成N个对象资源的清理,之后调用operator delete[]释放空间,实际调用operator delete释放空间
在调用delete[] 之后如何知道要释放对象的个数?
- 在new []对象时会把数组的大小存放起来,delete[]通过读取这个值来确定释放的对象的个数
定位new表达式
定位new表达式是在已分配的原始内存空间调用构造函数初始化一个对象
使用:
new (place_address) type;
new (place_address) type(initializer-list);
- place_address是一个指针,initializer-list是类型的初始化列表
void Test(){
Date *d = (Date *)malloc(sizeof(Date)); //d是指向一个Date类对象的指针,这个空间还不能算是一个对象,构造函数还没执行
new(d) Date(1970, 1, 1); //使用定位new表达式对d所指向的空间初始化
}
new/delete和malloc/free的区别
- new/delete和malloc/free都是从堆上申请空间,都需要用户进行手动释放
new/delete是操作符,malloc/free是函数
new申请空间可以进行初始化,malloc不可以
malloc申请空间时需要计算空间大小,new只需要加上类型即可
malloc的返回值是void*,使用是需要进行类型强转,new不需要
malloc申请空间时必须进行返回值判断,new不需要,new申请失败是会抛出异常
malloc/free在申请释放自定义类型时空间不会调用构造函数和析构函数,new在申请空间完成之后会调用构造函数,delete会先调用析构函数在进行空间释放
new/delete的效率比malloc/free低,new/delete在底层封装了malloc/free
面试题
-
设计一个类,这个类只能在堆上创建对象
构造函数和拷贝构造私有化,提供一个静态的成员函数,从堆上申请空间
class Date{
public:
static Date* GetDate(){
return new Date;
}
private:
Date()
{}
Date(const Date& d);
int _year;
int _month;
int _day;
};
-
设计一个类,只能在栈上创建对象
只能在栈上创建对象,把new的工能屏蔽掉即可,可以把operator new私有化
class StackOnly{
public:
StackOnly(){
}
private:
void* operator new(size_t size);
void operator delete(void* p);
};