[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` 指向另一个对象。
- 目录
- 基础知识
- 1、变量和基础类型
- 1.1、内置类型
- 1.2、变量
- 1.3、复合类型
- 1.4、类型修饰符
- 1.5、类型处理
- 1.6、自定义结构
- 1.7、数组
- 2、表达式和语句
- 2.1、运算符
- 2.2、语句
- 3、函数
- 1、语法相关
- 2、资源管理
- 3、面向对象
- 4、模板与泛型编程
- Problem01:判断类中是否包含函数
- Problem02:解析函数的参数类型
- 5、系统库
- Problem01:多线程维护最大值
- Problem02:介绍一下strcpy、strncpy、memcpy、memmove
- Problem03:介绍一下网络编程
- Problem04:select、poll、epoll的区别
- 未整理
- Problem11:实现在main函数前、后执行的函数
- Problem12:可变参函数的实现
- Problem13:全局变量初始化顺序问题
- Problem14:介绍一下隐式转换
- Problem07:实现一个不能被拷贝的类
- Problem08:实现一个只能通过动态、静态分配的类
- 开源项目
- redis
- 第一部分 数据结构与对象
- redis 底层数据结构
- redis 对象
- taskflow
- 数据结构
- Executor
