C++内存管理学习笔记

一.C/C++内存分布

我们先回顾在C语言学习阶段学习过的一张内存分布图:
在这里插入图片描述
然后我们可以根据上边的这幅图做一下下面这道笔试中一定会遇到的判断存储区的笔试题:

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";    
    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);
}
//选择题
 选项: A.栈  B.堆  C.数据段(全局区、静态区)  D.代码段(常量区)   
 globalVar在哪里?__C__   staticGlobalVar在哪里?_C__  
 staticVar在哪里?__C_   localVar在哪里?_A__   
 num1 在哪里?_A__      char2在哪里?__A_        
 *char2在哪里?_A_   pChar3在哪里?__A_      
 *pChar3在哪里?_D__   ptr1在哪里?__A_        
 *ptr1在哪里?__B_
 //填空题
 sizeof(num1) = _40_;//sizeof数组名是求数组的大小     
 sizeof(char2) = _5__;    strlen(char2) = __4_;   
 sizeof(pChar3) = _4|8_;   strlen(pChar3) = _4_; 
 sizeof(ptr1) = _4|8_;     sizeof(ptr2) = __4|8_;
 //4代表在32位的进程空间中,8代表在64位的进程空间中

各段内存说明:

  • 非静态局部变量/函数参数/返回值等存储在栈中,栈是向下增长
  • 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信
  • 堆用于程序运行时动态内存分配,堆是向上增长
  • 数据段存储全局数据和静态数据
  • 代码段存储可执行的代码和只读常量

二.回顾C语言中的动态内存管理

动态内存管理就是在堆上动态的分配和释放空间。C语言中动态分配的方式是malloc、realloc、calloc、free四个函数,前三个函数负责分配和调整空间,而free则负责释放前三个函数分配的空间。

1.面试题1:malloc/calloc/realloc的区别是什么?

void *malloc(size_t size);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);
void free(void *ptr);
  • malloc:malloc函数用来分配堆上的空间,按照字节为单位
  • calloc:calloc函数也是用来开辟堆上的空间,但和malloc的区别是它把这块空间初始化为0,也是按照字节为单位。nmemb指的是分配的字节数,size指的是每个成员占的字节数
  • realloc:realloc函数是用来调整已开辟的空间,调整方法为:如果该空间后边的空间足够扩大,则直接把后边的空间续上,源指针不变;如果该空间后边的空间不够扩大,则在内存中另外找一块空间,源指针改变指向新的空间,并且自动的将源空间free
  • free:free函数用来释放前三个函数开辟的空间

2.面试题2:32位平台指针为什么是4个字节?

在32位的机器上一个进程的地址空间也是32位的,那么一个虚拟地址就是由32个0、1串构成的,一个字节是8个比特位,那么一个地址就得用4个字节表示(32/8=4)。64位平台也是一样的道理,指针大小为8个字节。

3.面试题3:如何malloc一个大于3G的空间?

在一个32位的进程地址空间下,我们最多可以在堆上开辟的空间小于3G,因为有1G是操作系统的。我们要想在堆上申请到大于3G的空间可以考虑把运行这个进程的平台换成64位的,在64位的地址空间就可以申请到大于3G的堆空间。

三.C++中的内存管理

C++的语法是兼容C语言的语法,所以C语言中的malloc、realloc、calloc、free在C++中都适用。但是C++中由于内存分配失败的处理方法和C语言不一样C++处理失败的方法是抛异常,而C语言处理失败的方法是返回NULL,所以C++有自己独特的内存管理方法,既new、delete

1.new/delete操作内置类型用法

int main()
{
	 int* ptr1 = new int;//动态申请一个int类型的空间
	 int* ptr2 = new int(2);//动态申请一个int类型的空间并初始化为2
	 int* ptr3 = new int[3];//动态申请3个int类型的空间
	 int* ptr4 = new int[3]();//动态申请3个int类型的空间并初始化为0
	 //int* ptr5 = new int[3](1);不能这么初始化
	 delete ptr1;
	 delete ptr2;
	 delete[] ptr3;
	 delete[] ptr4;
	 return 0;
}

注::申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和 delete[]

2.new/delete操作自定义类型用法

class Date
{
public:
	 Date()
	 {}
	 Date(int year, int month, int day)
	  :_year(year)
	  , _month(month)
	  , _day(day)
	 {
	  cout << "date" << this << endl;
	 }
	 ~Date()
	 {
	  cout << "~date" << this << endl;
 	}
private:
	 int _year;
	 int _month;
	 int _day;
};
int main()
{
	 //C++中new/delete操作自定义类型
	 Date* d1 = new Date(2018, 11, 5);
	 Date* d2 = new Date[10]();//或者Date* d2 = new Date[10]
	 delete d1;
	 delete[] d2;
	 //C语言中malloc/delete操作自定义类型
	 cout << "------------------------" << endl;
	 Date* d3 = (Date*)malloc(sizeof(Date));
	 Date* d4 = (Date*)malloc(sizeof(Date)* 10);
	 free(d3);
	 free(d4);
	 return 0;
}

运行上边的程序,我们可以根据结果发现:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会。
在这里插入图片描述

四.operator new和operator delete函数

new和delete是用户进行动态内存申请和释放的操作符operator new 和operator delete是系统提供的全局函数new在底层调用operator new全局函数来申请空间delete在底层通过operator delete全局函数来释放空间

new(操作符)--->调用operator new(函数)--->调用malloc(函数)--->调用构造
//new失败抛异常(符合C++规范)
//malloc失败返回NULL
delete(操作符)-->调用析构--->调用operator delete(函数)--->free(函数)

operator new实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常operator delete最终是通过free来释放空间的。operator newoperator delete用户也可以自己实现,用户实现时即可实现成全局函数,也可实现成类的成员函数,但是一般情况下不需要实现,除非有特殊需求。

五.定位new表达式(placement-new)

定位new表达式是在已分配的原始内存空间调用构造函数初始化一个对象
使用格式:new (place_address) type或者new (place_address) type(initializer-list)。place_address必须是一个指针,initializer-list是类型的初始化列表
使用场景:定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化

class Date
{
public:
 Date()
 {}
 Date(int year, int month, int day)
  :_year(year)
  , _month(month)
  , _day(day)
 {
  cout << "date" << this << endl;
 }
 ~Date()
 {
  cout << "~date" << this << endl;
 }
private:
 int _year;
 int _month;
 int _day;
};
int main()
{
 //d1和d2现在还不是一个对象,因为没有调用构造函数初始化,只能说大小和对象的大小相同
 Date* d1 = (Date*)malloc(sizeof(Date));
 Date* d2 = (Date*)malloc(sizeof(Date));
 //调用new的定位表达式初始化
 new(d1)Date(2018, 11, 5);//调用有参构造
 new(d2)Date();//调用无参构造
}

六.常见面试题总结

1.malloc/free和new/delete的区别?

共同点:它们都是在堆上开辟空间,而且都需要手动释放
不同点:

  • malloc/free函数,而new/delete操作符new/delete在底层调用了malloc/free
  • malloc申请的空间不能直接初始化,而new申请的空间会调用构造函数,可以直接传参初始化
  • malloc申请空间时,需要手动计算空间大小并传递new只需在其后跟上空间的类型即可
  • malloc的返回值为void*, 在使用时必须强转new不需要,因为new后跟的是空间的类型
  • malloc申请空间失败时,返回的是NULL,因此使用时必须判空new不需要,但是new需要捕获异常
  • malloc/free只能申请内置类型的空间,不能申请自定义类型的空间,因为其不会调用构造与析构函数, 而new可以new在申请空间后会调用构造函数完成对象的构造delete在释放空间前会调用析构函数 完成空间中资源的清理
  • malloc申请的空间一定在堆上new不一定,因为operator new函数可以重新实现
  • new/deletemalloc/free效率稍微低点,因为new/delete的底层封装了malloc/free存在函数调用的开销

2.请设计一个类,该类只能在堆上创建对象

  • 将类的构造函数私有拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象
  • 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
#include<iostream>
using namespace std;
class HeapType
{
 public:
   static HeapType* CreateObj()
   {
     return new HeapType;
   }
   void Print()
   {
     cout << "HeapType:" << this << endl;
   }
 private:
   HeapType()//构造函数私有化
   {}
   //防拷贝
   HeapType(HeapType const&);
   // HeapType& operator=(HeapType const&);
};
int main()
{
   HeapType::CreateObj()->Print();
   HeapType::CreateObj()->Print();
   //HeapType ht; 这样不行,因为构造器是私有的,所以满足条件,不在栈上
   //HeapType ht1(HeapType::CreateObj());//拷贝构造也不行
   return 0;
}

在这里插入图片描述
由上述运行结果可以看出这是两个不同的对象

3.请设计一个类,该类只能在栈上创建对象

class StackType
{
 public:
   StackType()
   {}
   void Print()
   {
     cout << "StackType:" << this << endl;
   }
 private:
   //不能再堆上创建,我们可以考虑直接把operator new和new的定位表达式声明为私有的
   //将operator new声明为私有的,就把new的定位表达式也声明为私有的了
   void* operator new(size_t size);
   void operator delete(void* p);
};
int main()
{
  StackType st1;//ok 
  st1.Print();
  //StackType st2 = new StackType();//不行,因为operator new被屏蔽了
  //StackType* st3 = (StackType*)malloc(sizeof(StackType));
  //new(st3)StackType();//不行,因为new的定位表达式也被屏蔽
  return 0;
}

七.单例模式

有关单例模式的实现单独总结于我的另一篇博客:
https://blog.csdn.net/hansionz/article/details/83752531

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值