跳到主要内容 C++ 内存分布与模板机制详解 | 极客日志
C++ 算法
C++ 内存分布与模板机制详解 介绍 C++ 内存分布区域及动态内存管理方式,对比 C 语言 malloc/free 与 C++ new/delete 的差异,解析 operator new/delete 底层原理。同时阐述泛型编程思想,详细说明函数模板与类模板的定义、实例化过程及参数匹配原则,帮助掌握 C++ 核心内存模型与模板技术。
利刃 发布于 2026/3/27 更新于 2026/4/18 2 浏览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
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
栈 :又叫堆栈,非静态局部变量、函数参数、返回值等在此区域,栈是向下增长的。
内存映射段 :高效的 I/O 映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存,做进程通信。
堆 :用于程序运行时的动态内存分配,堆是可以向上增长的。
数据段 :存储全局数据和静态数据。
代码段 :可执行的代码和只读常量。
2. C 语言中动态内存管理方式:malloc/calloc/realloc/free
3. C++ 内存管理方式 C 语言内存管理方式在 C++ 中可以继续使用,但有些地方无能为力,而且使用起来比较麻烦,因此 C++ 又提出了自己的内存管理方式:通过 new 和 delete 操作符进行动态内存管理 。
3.1. new/delete 操作内置类型 void Test () {
int * ptr4 = new int ;
int * ptr5 = new int (10 );
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 () {
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 全局函数来释放空间。
void * __CRTDECL operator new (size_t size) _THROW1 (_STD bad_alloc) {
void * p;
while ((p = malloc (size)) == 0 )
if (_callnewh(size) == 0 ) {
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
void operator delete (void * pUserData) {
_CrtMemBlockHeader* pHead;
RTCCALLBACK (_RTC_Free_hook, (pUserData, 0 ));
if (pUserData == NULL ) return ;
_mlock(_HEAP_LOCK);
__TRY
pHead = pHdr (pUserData);
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg(pUserData, pHead->nBlockUse);
__FINALLY _munlock(_HEAP_LOCK);
__END_TRY_FINALLY
return ;
}
#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. 自定义类型
调用 operator new 函数申请空间。
在申请的空间上执行构造函数,完成对象的构造。
在空间上执行析构函数,完成对象中资源的清理工作。
调用 operator delete 函数释放对象的空间。
调用 operator new[] 函数,在 operator new[] 中实际调用 operator new 函数完成 N 个对象的申请。
在申请的空间上执行 N 次构造函数。
在释放的对象空间上执行 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);
return 0 ;
}
int main (void ) {
int a = 10 ;
double b = 20.0 ;
Add <int >(a, b);
return 0 ;
}
如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功,编译器将会报错。
一个非目标函数可以和一个同名的模板函数同时存在,而且该函数模板还可以被实例化为这个非模板函数。
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 );
}
对于非模板函数和同名函数模板,如果其他条件都相同,在调用时会优先调用非模板函数而不会从该模板产生一个实例,如果模板可以产生一个具有更好匹配的函数,那么将选择模板。
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 );
}
模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
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;
};
template <class T >
void Stack<T>::Push (const T& data) {
++_size;
}
int main () {
Stack<int > st1;
Stack<double > st2;
return 0 ;
}
类模板的实例化 :类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
Stack<int > st1;
Stack<double > st2;