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来释放空间的.