1. 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";
const 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);
}
这段代码里:
- 位于栈区的有:localVar, num1, char2, pchar3, ptr1
- 位于堆区的有:*ptr1
- 位于静态区的有:globalVar, staticGlobalVar, staticVar
- 位于常量区的有:*pchar3

- 栈:又叫堆栈,非静态局部变量、函数参数、返回值等在此区域,栈是向下增长的。
- 内存映射段:高效的 I/O 映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存,做进程通信。
- 堆:用于程序运行时的动态内存分配,堆是可以向上增长的。
- 数据段:存储全局数据和静态数据。
- 代码段:可执行的代码和只读常量。
2. C 语言中动态内存管理方式:malloc/calloc/realloc/free

3. C++ 内存管理方式
C 语言内存管理方式在 C++ 中可以继续使用,但有些地方无能为力,而且使用起来比较麻烦,因此 C++ 又提出了自己的内存管理方式:通过 new 和 delete 操作符进行动态内存管理。
3.1. new/delete 操作内置类型
void Test() {
// 动态申请一个 int 类型的空间
int* ptr4 = new int;
// 动态申请一个 int 类型的空间并初始化为 10
int* ptr5 = new int(10);
// 动态申请 10 个 int 类型的空间
int* ptr6 = new int[3];
delete ptr4;
delete ptr5;
delete[] ptr6;
}

申请和释放单个元素的空间,使用 new 和 delete 操作符;申请和释放连续的空间,使用 new[] 和 delete[],应该注意匹配。
3.2. new 和 delete 操作自定义类型
#include <iostream>
using namespace std;
class A {
public:
A(int a = 0) :_a(a) { cout << "A():" << this << endl; }
~A() { cout << "~A():" << this << endl; }
private:
int _a;
};
int main() {
// new/delete 和 malloc/free 最大区别是 new/delete 对于【自定义类型】除了开空间还会调用构造函数和析构函数
A* p1 = (A*)malloc(sizeof(A));
A* p2 = new A(1);
free(p1);
delete p2;
// 内置类型是几乎是一样的
int* p3 = (int*)malloc(sizeof(int));
int* p4 = new int;
free(p3);
delete p4;
A* p5 = (A*)malloc(sizeof(A)*10);
A* p6 = new A[10];
free(p5);
delete[] p6;
return 0;
}
4. operator new 与 operator delete 函数
4.1. operator new 与 operator delete 函数
new 和 delete是用户进行动态内存申请和释放的操作符,operator new 和 operator delete是系统提供的全局函数。new 在底层调用 operator new全局函数来申请空间,delete 在底层调用 operator delete全局函数来释放空间。
/* operator new:该函数实际通过 malloc 来申请空间,当 malloc 申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则 */
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc) {
// try to allocate size bytes
void* p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0) {
// report no memory
// 如果申请内存失败了,这里会抛出 bad_alloc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
/* operator delete: 该函数最终是通过 free 来释放空间的 */
void operator delete(void* pUserData) {
_CrtMemBlockHeader* pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL) return;
_mlock(_HEAP_LOCK); /* block other threads */
__TRY /* get a pointer to memory block header */
pHead = pHdr(pUserData);
/* verify block type */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg(pUserData, pHead->nBlockUse);
__FINALLY _munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY
return;
}
/* free 的实现 */
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
通过上述两个全局函数的实现知道,operator new 实际也是通过 malloc 来申请空间,如果 malloc 申请空间成功就返回,否则执行用于提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过 free 来释放空间的。
5. new 和 delete 的实现原理
5.1. 内置类型
如果申请的是内置类型空间,new 和 malloc,delete 和 free 基本类似,不同的地方是:new/delete 申请和释放的是单个元素空间,new[] 和 delete[] 申请的是连续空间,而且 new 在申请空间失败时会抛异常,malloc 会返回 NULL。
5.2. 自定义类型
new 的原理:
- 调用 operator new 函数申请空间。
- 在申请的空间上执行构造函数,完成对象的构造。
delete 的原理:
- 在空间上执行析构函数,完成对象中资源的清理工作。
- 调用 operator delete 函数释放对象的空间。
new T[N] 的原理:
- 调用 operator new[] 函数,在 operator new[] 中实际调用 operator new 函数完成 N 个对象的申请。
- 在申请的空间上执行 N 次构造函数。
delete[] 原理:
- 在释放的对象空间上执行 N 次析构函数完成 N 个对象中资源的清理。
- 调用 operator delete[] 释放空间,实际在 operator delete[] 中调用 operator delete 来释放空间。
6. malloc/free 和 new/delete 的区别
malloc/free 和 new/delete 的共同点是都从堆上申请空间,并且需要用户手动释放。不同的地方是:
- malloc 和 free 是函数,new 和 delete 是操作符。
- malloc 申请的空间不会初始化,new 可以初始化。
- malloc 申请空间时,需要手动计算空间大小并传递,new 只需要在其后跟上空间的类型即可,如果是多个对象,[] 中指定对象个数即可。
- malloc 的返回值为 void*,在使用时必须强转,new 不需要,因为 new 后跟的是空间的类型。
- malloc 申请空间失败时,返回的是 NULL,因此使用时必须判空,new 不需要,但是 new 需要捕获异常。
- 申请自定义类型对象时,malloc/free 只会开辟空间,不会调用构造函数与析构函数,而 new 在申请空间后会调用构造函数完成对象的初始化,delete 在释放空间前会调用析构函数完成空间中资源的清理释放。
7. 泛型编程
void Swap(int& left, int& right) {
int temp = left; left = right; right = temp;
}
void Swap(double& left, double& right) {
double temp = left; left = right; right = temp;
}
void Swap(char& left, char& right) {
char temp = left; left = right; right = temp;
}
对于 swap() 函数想要实现通用的效果,可以使用函数重载,但是会有不好的地方:
- 重载的函数仅仅是类型不同,代码复用率低,只要有新的类型出现时,就需要增加对应的函数。
- 代码的可维护性比较低,一个出错可能所有重载都出错。
这里就要提出泛型编程的概念:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。
8. 函数模板
函数模板的概念:函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
函数模板的格式:template< typename T1, typename T2, ..., typename TN> 返回值类型 函数名(参数列表){}
template<typename T>
void Swap(T& left, T& right) {
T temp = left; left = right; right = temp;
}
typename用来定义模板参数关键字,也可以使用class,但是不能用struct代替class;
函数模板的原理:函数模板是一个蓝图,本身不是一个函数,是编译器用使用方式产生特定类型函数的模具,所以其实模板就是把本来应该我们做的重复的事情交给了编译器。在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。
函数模板的实例化:用不同类型的参数使用函数模板时,称为函数模板的实例化,模板参数实例化分为:隐式实例化和显式实例化。
隐式实例化
template<class T> T Add(const T& left, const T& right) {
return left + right;
}
int main() {
int a1 = 10, a2 = 20;
double d1 = 10.0, d2 = 20.0;
Add(a1, a2);
Add(d1, d2);
// 该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型
// 通过实参 a1 将 T 推演为 int,通过实参 d1 将 T 推演为 double 类型,但模板参数列表中只有一个 T,
// 编译器无法确定此处到底该将 T 确定为 int 或者 double 类型而报错
// 注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅
// Add(a1,d1);
// 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化
// Add(a, (int)d);
return 0;
}
显式实例化
int main(void) {
int a = 10;
double b = 20.0;
// 显式实例化
Add<int>(a, b);
return 0;
}
如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功,编译器将会报错。
模板参数的匹配原则
- 一个非目标函数可以和一个同名的模板函数同时存在,而且该函数模板还可以被实例化为这个非模板函数。
// 专门处理 int 的加法函数
int Add(int left, int right) {
return left + right;
}
// 通用加法函数
template<class T> T Add(T left, T right) {
return left + right;
}
void Test() {
Add(1, 2); // 与非模板函数匹配,编译器不需要特化
Add<int>(1, 2); // 调用编译器特化的 Add 版本
}
- 对于非模板函数和同名函数模板,如果其他条件都相同,在调用时会优先调用非模板函数而不会从该模板产生一个实例,如果模板可以产生一个具有更好匹配的函数,那么将选择模板。
// 专门处理 int 的加法函数
int Add(int left, int right) {
return left + right;
}
// 通用加法函数
template<class T1,class T2> T1 Add(T1 left, T2 right) {
return left + right;
}
void Test() {
Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的 Add 函数
}
- 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
9. 类模板的定义格式
template<class T1,class T2,...,class Tn>
class 类模板名 {
// 类内成员定义
};
#include <iostream>
using namespace std;
// 类模版
template<typename T>
class Stack {
public:
Stack(size_t capacity = 4) {
_array = new T[capacity];
_capacity = capacity;
_size = 0;
}
void Push(const T& data);
private:
T* _array;
size_t _capacity;
size_t _size;
};
// 模版不建议声明和定义分离到两个文件.h 和.cpp会出现链接错误,具体原因后面会讲
template<class T>
void Stack<T>::Push(const T& data) {
// 扩容 _array[_size]= data;
++_size;
}
int main() {
Stack<int> st1; // int
Stack<double> st2; // double
return 0;
}
类模板的实例化:类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
// Stack 是类名,Stack<int>才是类型
Stack<int> st1; // int
Stack<double> st2; // double


