C++中的五种内存
在C++中内存分为五个区:堆、栈、自由存储区、全局/静态存储区和常量存储区。
堆区:用户使用new获得的内存在这里。用户需要自行管理其声明周期,也就是说一个new要对应一个delete,如果因为某些原因(之后我会说明一些可能的原因)内存没有被释放,那么在程序结束后,会由操作系统自行回收,这显然不是我们想看到的。
栈区:存储局部变量、函数参数等,比方说你在某个函数里定义了一个int变量a,这个a就存放在栈区。这块内存的生命周期由系统管理,不需要我们去操心。
自由存储区:用malloc分配的内存放置在这里。这块内存和堆很相似,不过是使用free来释放内存的。
全局/静态存储区:存放全局变量和静态变量。
常量存储区:存放常量,不允许更改。
new和delete
void testDelete(){ int *p = new int; cout << *p << endl; cout << &p << endl; *p=1; cout << *p << endl; cout << &p << endl; delete p; cout << *p << endl; cout << &p << endl; p = nullptr; //程序到此停止执行 cout << *p << endl; cout << &p << endl; }
可以看出 delete指针并非将该指针弃置不用,而是将其指向的内存中的数据清除,但是指针仍然指向原来的内存!
如果我们想按照delete的英文本意,把这个指针从世界上彻底销毁,需要将指针的值赋为nullptr
智能指针
如果可以,我们应该使用STL提供的shared_ptr和unique_ptr替换原始指针,这样我们可以不用自行管理内存的生命周期,获得类似JAVA和C#的自动内存回收体验。
shared_ptr<int> p = make_shared<int>(1);
shared_ptr<int> q(new int(2));
注意:
不要使用相同的内置指针初始化(或reset)多个智能指针。
不delete get()返回的指针
不适用get()初始化或reset另一个智能指针
如果使用了get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了。
不过你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器。
allocator类
该类帮助我们将内存分配和对象构造分离开来,它分配的内存是原始的、未构造的。
allocator<string> alloc;//可以分配string的allocator对象
auto const p = alloc.allocate(n);//分配n个未初始化的string
malloc/free
从C程序员转换过来的C++程序员总是有个困惑:new/delete到底究竟和C语言里面的malloc/free比起来有什么优势?或者是一样的?
malloc/free只是对内存进行分配和释放;new/delete还负责完成了创建和销毁对象的任务。
new的安全性要高一些,因为他返回的就是一个所创建的对象的指针,对于malloc来说返回的则是void*,还要进行强制类型转换,显然这是一个危险的漏洞。
我们可以对new/delete重载,使内存分配按照我们的意愿进行,这样更具有灵活性,malloc则不行。
不过,new/delete也并不是十分完美,大概最大的缺点就是效率低(针对的是缺省的分配器),原因不只是因为在自由存储区上分配(和栈上对比),而且new只是对于堆分配器(malloc/realloc/free)的一个浅层包装,没有针对小型的内存分配做优化。另外缺省分配器具有通用性,它管理的是一块内存池,这样的管理往往需要消耗一些额外空间。我们可以针对new/delete进行重写以追求更高的效率,对于这方面更深入的探讨可以参考《Effective C++》第八章。
static void* operator new(size_t sz);
static void operator delete(void* p);
new 对象实际上做了2件事:调用opeator new,在自由存储区分配一个sizeof(A)大小的内存空间;然后调用构造函数A(),在这块内存空间上类砖砌瓦,建造起我们的对象。
同样对于delete,则做了相反的两件事:调用析构函数~A(),销毁对象,调用operator delete,释放内存。