C++内存管理、模板初阶简介
代码语言:javascriptAI代码解释class Apublic:: _a(a)~A()private:int _a;int main()// new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数free(p1);delete p2;// 内置类型是几乎是一样的// Cfree(p3);delete p4;fre
C/C++内存分布
我们先来看看下面的这一段代码和相关问题
代码语言:javascript
AI代码解释
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);
}

程序运行时的内存分区(栈、堆、数据段 / 静态区、代码段 / 常量区),本质是操作系统和编译器为了适配不同数据的特性(生命周期、访问频率、可修改性、分配方式),对内存进行的精细化管理—不同区域有不同的管理规则,最终实现内存使用效率更高、程序运行更稳定、资源管控更灵活的目标. 下面结合每个区域的核心特性,解释为什么必须划分这些区: 一、代码段(常量区):只读、共享、持久,适配 “执行 / 只读数据” 的需求. 代码段存储程序的机器指令、字符串常量(如 “abcd”)、const 修饰的只读常量,划分这个区的核心原因: ①防止误修改,保证程序安全:指令是程序运行的核心,字符串常量是不可变的,把它们放到只读内存区域(操作系统会标记该区域为只读),能避免代码/常量被程序意外改写(比如如果指针误写代码段,会直接触发内存保护错误,而不是悄悄改了指令导致程序崩溃). ②节省内存,支持共享:多个进程如果运行同一个程序(比如同时打开多个记事本),它们的指令部分可以共享同一块代码段内存,不用每个进程都复制一份指令,大幅节省系统内存. ③优化执行效率:代码段的内容固定不变,CPU 缓存可以针对性优化(缓存命中率更高),提升程序执行速度. 二、数据段(静态区):持久、可修改,适配 “全局 / 静态数据” 的需求 数据段存储全局变量、静态变量(static)细分初始化数据段(已赋值)和未初始化数据段(未赋值),划分这个区的核心原因: ①适配 “持久生命周期” 的需求:全局/静态变量的生命周期和程序一致(从程序启动到退出),不需要像局部变量那样频繁创建 / 销毁,单独划分区域可以让操作系统在程序启动时一次性分配内存,退出时统一释放,避免和 “动态内存(栈/堆)” 混淆. ②优化内存占用和初始化效率:(未初始化的全局/静态变量):操作系统会在程序启动时自动将其清 0,且 不需要存储在可执行文件中(只记录大小),能大幅减小可执行文件的体积; 已初始化的静态数据:编译器提前确定其大小和初始值,程序启动时直接加载到内存,无需运行时动态计算. ③保证全局可见性:全局变量需要在整个程序范围内访问,单独的静态区能让编译器/操作系统统一管理其内存地址,避免被栈/堆的动态内存覆盖或干扰. 三、栈区:自动、高效,适配 “局部 / 临时数据” 的需求 栈区存储局部变量、函数参数、函数调用上下文(返回地址、栈帧指针),划分这个区的核心原因: ①极致的分配/销毁效率:栈遵循先进后出(FILO)规则,内存分配/释放仅通过移动栈指针(CPU 的ESP/EBP 寄存器)完成,不需要复杂的内存分配算法(比如堆的空闲链表查找),速度比堆快几个数量级 —而局部变量(如localVar、num1数组)的生命周期仅在函数调用内,正好适配这种 “快速创建、自动销毁” 的特性. ②自动管理,减少人为错误:栈的内存由编译器/CPU 自动维护(函数进入时入栈,函数返回时出栈),程序员无需手动申请/释放,避免了堆内存常见的 “内存泄漏”“重复释放” 等问题. ③隔离性和安全性:栈的大小是固定的(通常几 MB),且每个线程有独立的栈,不同线程的局部变量互不干扰;栈溢出(比如递归太深)会被系统及时检测并终止,避免影响整个程序的内存空间. 四、堆区:动态、灵活,适配 “手动管理的动态数据” 的需求 堆区存储malloc/new分配的动态内存(如*ptr1),划分这个区的核心原因: ①突破栈的限制,适配动态需求:栈的大小有限(默认几 MB),无法存储大内存(比如大数组、大对象); 很多数据的生命周期无法提前确定(比如运行时根据用户输入创建的对象),栈的 “函数结束即销毁” 规则不适用 —堆允许程序员手动控制内存的分配(何时申请)和释放(何时free/delete),生命周期完全由代码决定. ②灵活的内存分配:堆的大小仅受物理内存/虚拟内存限制,支持任意大小、任意时机的内存请求,能满足程序运行时的动态内存需求(比如链表、树、动态数组的内存分配).

【说明】 1️⃣栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的. 2️⃣内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库.用户可使用系统接口创建共享共享内存,做进程间通信.(小编后续学习到Linux系统会重点介绍). 3️⃣堆用于程序运行时动态内存分配,堆是可以上增长的. 4️⃣数据段--存储全局数据和静态数据. 5️⃣代码段--可执行的代码/只读常量.
2. C语言中动态内存管理方式:malloc/calloc/realloc/free
代码语言:javascript
AI代码解释
void Test ()
{
// 1.malloc/calloc/realloc的区别是什么?
int* p2 = (int*)calloc(4, sizeof (int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
// 这里需要free(p2)吗?不需要.
//原因:realloc的行为是——若重新分配内存成功,原内存块(p2指向的内存)会被自动释放,此时p2会变成“悬空指针”(指向已释放的内存).
//若此时再执行free(p2),会导致重复释放内存,触发未定义行为(程序崩溃、内存异常等).
//当前代码中free(p3)已经释放了realloc分配的新内存,无需再处理p2.
free(p3 );
}
|
函数 |
函数原型(简化) |
功能描述 |
|---|---|---|
|
malloc |
void* malloc(size_t size) |
分配size 字节的未初始化内存 |
|
calloc |
void* calloc(size_t num, size_t size) |
分配num 个 size 字节的内存 |
|
realloc |
void* realloc(void* ptr, size_t size) |
调整ptr指向的内存块大小为size 字节 |
malloc的实现原理是什么?可以看看这个视频 Glibc中malloc实现原理
3.C++内存管理方式
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理.
3.1new/delete操作内置类型
代码语言:javascript
AI代码解释
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.2new和delete操作自定义类型
代码语言:javascript
AI代码解释
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)); // C
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;
}
注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会.
4.operator new与operator delete函数(重点)
new和delete是用户进行动态内存申请和释放的操作符,operator new和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间.
代码语言:javascript
AI代码解释
/*
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来释放空间的.
更多推荐



所有评论(0)