NIUCLOUD是一款SaaS管理后台框架多应用插件+云编译。上千名开发者、服务商正在积极拥抱开发者生态。欢迎开发者们免费入驻。一起助力发展! 广告
[TOC] ## 内存分区 **堆区**:为程序动态申请的内存提供,一般需要程序自己释放所申请的空间,否则需要等待程序结束时由操作系统回收; **栈区**:由系统进行内存的管理理,存放为了运行函数而分配的局部变量、函数参数、返回数据、返回地址等; **静态储存区**:静态存储区内的变量在程序编译阶段已经分配好内存空间并初始化,一般存放全局变量、静态变量和常量数据; **代码区**:存放程序体的二进制代码; > 堆 vs. 自由存储区:堆是操作系统维护的一块内存,而自由存储是C++中通过new与delete 动态分配和释放对象的抽象概念。虽然基本上所有的C++编译器默认使用堆来实现自由存储区,但堆与自由存储区并不等价。 ## 内存泄漏 **内存泄漏**指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费。 **常见的几种原因**: * 动态申请(malloc/new)了堆上的空间,但是没有释放; * 在类的构造函数和析构函数中没有匹配的调用new和delete函数; * 没有正确地清除嵌套的对象指针; * 没有配对使用 new/delete、new[]/delete[]; * 没有将基类的析构函数定义为虚函数; * 使用 `bzero` 清空一个带非 POD 类型成员变量的类或者结构体; ## 内存对齐 C++空类的内存大小为1字节,为了保证其对象拥有彼此独立的内存地址。 成员变量在类中的内存存储并不一定是连续的。它是按照编译器的设置,按照内存块来存储的,这个内存块大小的取值,就是内存对齐。 * 第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照 `#pragma pack` 指定的数值和这个数据成员自身长度中,比较小的那个进行。 * 在数据成员完成各自对齐之后,类(结构体)本身也要进行对齐,对齐将按照`#pragma pack` 指定的数值和结构最大数据成员长度中,比较小的那个进行。 * `#pragma pack` 合法数值有 1、2、4、8、16。 **为什么要进行内存对齐?** 4字节存取粒度的处理器取int类型变量(32位系统),该处理器只能从地址为4的倍数的内存开始读取数据。 假如没有内存对齐机制,数据可以任意存放,现在一个int变量存放在从地址1开始的连续四个字节地址中,当处理器去取数据时,要先从0地址开始读取第一个4字节块,剔除不想要的字节(0地址),然后从地址4开始读取下一个4字节块,同样剔除不要的数据(5,6,7地址),最后留下的两块数据合并放入寄存器.这需要做很多工作。 ## 内存池 内存池指是在真正使用内存之前,先申请分配一定数量的、大小相等的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是,使得内存分配效率得到提升。 **内存池的优势**: * 减少new和delete函数的调用次数,减少运行时间(系统在分配和释放内存时,需要查看和修改内存空闲块表,有时还需要考虑线程安全的问题); * 避免内存碎片; * 统一释放内存,避免内存泄漏; ## new/delete 和 malloc/free 的区别 1. 分配内存的位置不同:malloc函数从堆区获取内存空间,而new从自由存储区获取内存空间; 2. 分配成功的返回值:malloc函数返回 `void*`,而new函数返回完整的类型指针; 3. 分配失败的返回值:malloc函数返回空指针 `NULL`,而new函数默认抛出异常; 4. 分配内存的大小:malloc函数需要显式地指定字节数,而new函数根据类型自动计算; 5. 扩充已分配的内存:malloc函数可以使用realloc函数扩充,而new无法直观地处理; 6. 处理数组:malloc需要显式计算数组大小,而new有专门的new\[\]负责处理数组; 7. 构造和析构函数:malloc函数不会调用类的构造和析构函数,但new函数会; 8. 释放空间:malloc函数对应使用free函数释放空间,而new函数则使用delete释放空间; ## free如何确定对应释放空间的大小 地址前有块空间记录了当前申请空间的大小。 ``` ____ The allocated block ____ /                             \ +--------+--------------------+ | Header | Your data area ... | +--------+--------------------+         ^         |         +-- The address you are given ``` ## 智能指针 `unique_ptr`,`shared_ptr`,`weak_ptr` 为c++11标准,而 `auto_ptr` 已被c++11标准弃用。 ### 为什么要使用智能指针? 智能指针的作用是管理一个指针,防止内存泄漏,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。 ### auto_ptr [`auto_ptr`](https://en.cppreference.com/w/cpp/memory/auto_ptr) 的实现原理其实就是RAII,在构造的时候获取资源,在析构的时候释放资源,并进行相关指针操作的重载,使用起来就像普通的指针。该智能指针已经在C++11中被弃用了,已经有更好的 `unique_ptr` 来代替。 **1. auto\_ptr没有使用引用计数,在复制构造函数和赋值构造函数中将对象所有权转移了。** ```c++template<typename _Tp1> auto_ptr(auto_ptr<_Tp1>& __a) throw() : _M_ptr(__a.release()) { } template<typename _Tp1>auto_ptr& operator=(auto_ptr<_Tp1>& __a) throw() { reset(__a.release()); return *this; } void reset(element_type* __p = 0) throw() { if (__p != _M_ptr) { delete _M_ptr; _M_ptr = __p; } } element_type* release() throw() { element_type* __tmp = _M_ptr; _M_ptr = 0; return __tmp; } ``` 因此使用该智能指针的时候要注意: ``` c++ #include <stdio.h> #include <memory> class A { public: A(int data) : _data(data) { printf("create A[%d]\n", _data); } ~A() { printf("destory A[%d]\n", _data); } int data() const { return _data; } private: int _data; }; int main () { { std::auto_ptr<A> pa(new A(1)); printf("data = %d\n", pa->data()); std::auto_ptr<A> pb = pa; // 指针所有权发生变化,但编译可通过 printf("data = %d\n", pa->data()); // 编译可通过,但运行到这里会发生异常 } return 0; } ``` **2. auto\_ptr不能指向数组,因为auto\_ptr在析构的时候只是调用delete,而数组应该要调用delete[]。** **3.auto\_ptr不能和标准容器(vector,list,map…)一起使用** STL容器中的元素经常要支持拷贝,赋值等操作,在这过程中auto\_ptr会传递所有权。 ### unique_ptr [`unique_ptr`](https://en.cppreference.com/w/cpp/memory/unique_ptr) 实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。它对于避免资源泄露(例如“以 new 创建对象后因为发生异常而忘记调用 delete”)特别有用。 ``` c++ #include <stdio.h> #include <memory> class A { public: A(int data) : _data(data) { printf("create A[%d]\n", _data); } ~A() { printf("destory A[%d]\n", _data); } int data() const { return _data; } private: int _data; }; int main () { { std::unique_ptr<A> pa(new A(1)); printf("data = %d\n", pa->data()); //std::unique_ptr<A> pb = pa; // 编译不通过 std::unique_ptr<A> pb = std::move(pa); // 可以通过std::move 转移所有权 printf("data = %d\n", pb->data()); } return 0; } ``` ### shared_ptr [`shared_ptr`](https://en.cppreference.com/w/cpp/memory/shared_ptr) 实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。 ```c++ #include <stdio.h> #include <memory> class A { public: A(int data) : _data(data) { printf("create A[%d]\n", _data); } ~A() { printf("destory A[%d]\n", _data); } int data() const { return _data; } private: int _data; }; int main () { std::shared_ptr<A> pb; { std::shared_ptr<A> pa(new A(1)); printf("data = %d\n", pa->data()); pb = pa; printf("refer count[%ld]\n", pb.use_count()); } printf("after free pa, refer count[%ld]\n", pb.use_count()); // 当pb生命周期结束时,释放空间 return 0; } ``` ### weak_ptr [`weak_ptr`](https://en.cppreference.com/w/cpp/memory/weak_ptr) 是一种不控制对象生命周期的智能指针, 它指向一个 `shared_ptr` 管理的对象,进行该对象的内存管理的是那个强引用的 `shared_ptr`。 ### 智能指针内存泄漏 当两种对象相互使用一个 `shared_ptr` 成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。 解决方法,将一种对象改用 `weak_ptr` 指向另一个对象。