🎈 个人主页👉: tbRNA-CSDN博客
💯 个人简介:在校大学生一枚💋.
😍 希望我的文章对大家有着不一样的帮助,欢迎大家关注我,感谢大家的多多支持!🎉 欢迎 👍点赞 ✍评论 ⭐收藏
往期文章👇
目录
[1. C / C++ 内存分布](#1. C / C++ 内存分布)
[2. C语言中动态内存管理方式](#2. C语言中动态内存管理方式)
[一、malloc vs calloc](#一、malloc vs calloc)
[3. C++ 内存管理方式](#3. C++ 内存管理方式)
[一、new / delete操作内置类型](#一、new / delete操作内置类型)
[4. operator new与operator delete函数](#4. operator new与operator delete函数)
[5. new和delete的实现原理](#5. new和delete的实现原理)
[😋 new的原理](#😋 new的原理)
[😜 delete的原理](#😜 delete的原理)
[🤪 new [N] 的原理](#🤪 new [N] 的原理)
[🤗 delete[]的原理](#🤗 delete[]的原理)
[6. malloc / free 和 new / delete的区别](#6. malloc / free 和 new / delete的区别)
1. C / C++ 内存分布
cpp
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);
}
请填写各变量 / 表达式对应的内存区域:
选项:A. 栈 B. 堆 C. 数据段 (静态区) D. 代码段 (常量区)

【说明】
- 栈又叫堆栈:非静态局部变量 / 函数参数 / 返回值等等,栈是向下增长的。
栈区用于存储函数调用时的局部变量、函数参数以及返回地址。当函数调用完成后,分配给这个函数的栈空间会被释放。
cpp
#include <iostream>
using namespace std;
void function(int a, int b)
{
int localVar = a + b;
cout << localVar << endl;
}
int main() {
function(3, 4);
return 0;
}
在这个例子中,a、b和localVar都是局部变量,它们存放在栈区。
当 function 函数调用结束后,对应的函数栈所占用的空间(参数 a、b,局部变量 localVar)都会被回收。
- 内存映射段是高效的I / O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口
创建共享共享内存来做进程间通信。
-
堆用于程序运行时动态内存分配,堆是可以上增长 的,堆区是用于动态内存分配的区域,当使用new(C++) 或者**malloc(C)**分配内存时,分配的内存块就位于堆区。
-
数据段:存储全局数据和静态数据。
-
代码段:可执行的代码 / 只读常量。
全局 / 静态存储区和常量存储区的对比

2. C语言中动态内存管理方式
C语言的动态内存函数都在**<stdlib.h>** 头文件中,返回类型都是void* ,需要类型转换。
它们操作在堆上,堆内存不像栈那样自动释放,必须手动管理。
否则,就可能出现野指针:指向已释放内存的指针
一、malloc vs calloc
malloc 是最基础的分配函数,它向内存申请一块连续可用的空间,并返回指向这块空间的指针。calloc 函数也用来动态内存分配。
它们的原型和功能如下:
void* malloc (size_t size);
void* calloc (size_t num, size_t size);
|-----|---------------------------------------|---------------------------------|
| 特性 | malloc | calloc |
| 功能 | 申请指定字节大小的内存 | 为 num 个大小为 size 的元素开辟空间 |
| 初始值 | 随机垃圾值(未初始化) | 全为 0(每个字节初始化为 0) |
| 参数 | 需手动计算总大小(如 10 * sizeof ( int ) ) | 自动计算(如 10, sizeof ( int ) ) |
| 性能 | 极快(只分配,不清理) | 稍慢(分配 + 清零) |
| 安全性 | 低 若不立刻赋值,读取即乱码 | 高 指针成员默认为 NULL,整数为 0 |
😍如果你在为结构体数组分配内存 ,建议使用 calloc 。
原因:结构体中往往包含指针。如果用 malloc,这些指针指向的是野指针,一旦误用直接导致崩溃。而 calloc 会把它们初始化为 NULL。
这是更安全的防御性编程,你可以安全检查if ( ptr -> member == NULL)。
二、free:内存回收与释放
free 函数 专门用来做动态内存的释放和回收。
它的原型是:
void free (void* ptr);
😘注意:
-
如果参数 ptr 指向的空间不是动态开辟的,那 free 函数的行为是未定义的,比如释放栈上的局部变量。
-
如果参数 ptr 是 NULL 指针,则 free函数什么事都不做。
-
free 函数用来释放动态开辟的内存。
🥰建议:
-
ptr 必须是malloc / calloc / realloc返回的指针,否则行为未定义。
-
free(NULL) 没事,但 free 非动态内存(如栈变量)会崩溃。
-
记住:free 后,ptr 仍指向旧地址,但内存已无效 ------ 这就是野指针的来源。
-
建议 free 后总是跟着设 ptr = NULL。
三、realloc:不仅是扩容
realloc 函数让动态内存管理更加灵活,可以对动态开辟内存大小进行调整。
它的原型是:
void* realloc (void* ptr, size_t size);
其中,ptr 是要调整的内存地址,size 是调整之后的新大小。
😉它的底层行为有两种:
行为 A:原地扩容(原有空间之后有足够大的空间)
直接在原有内存之后追加空间,原来空间的数据不发生变化。指针地址不变 。
行为 B:异地搬家(原有空间之后没有足够大的空间)
在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址 。同时,它会将原来内存中的数据移动到新的空间。
🙃陷阱:旧指针失效
这是个隐形陷阱。在异地搬家发生后,原指针 ptr 指向的内存块被释放,如果你继续使用原指针,就是严重的 Use-After-Free 错误。
四、总结
写C代码时严格遵守以下模板,养成习惯,能避开 90% 坑:
-
malloc 成功返回一个指向开辟好空间的指针,如果开辟失败,则返回一个 NULL 指针 ,因此 malloc 的返回值一定要做检查 。
-
free(p); 后,立刻执行p = NULL; ,这既防止了双重释放,也防止了悬空指针导致的 Use-After-Free。
-
使用 realloc 时,永远不要直接赋值给原指针 !
-
避免越界,用循环时,严格 < size,或用安全函数如strncpy。
(strncpy用法可以参考这篇文章C语言基础 字符串函数)
3. C++ 内存管理方式
😚C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦
因此 C++又提出了自己的内存管理方式:通过 new 和 delete 操作符进行动态内存管理.
一、new / delete操作内置类型
cpp
void Test()
{
// 动态申请一个int类型的空间
int* ptr1 = new int;
// 动态申请一个int类型的空间并初始化为10
int* ptr2 = new int(10);
// 动态申请3个int类型的空间
int* ptr3 = new int[3];
// 动态申请10个int类型的空间并初始化为0
int* ptr4 = new int[10] {0};
// 动态申请10个int类型的空间并将前5个初始化为1,2,3,4,5剩余自动初始化为0
int* ptr5 = new int[10] {1, 2, 3, 4, 5};
delete ptr1;
delete ptr2;
delete[] ptr3;
delete[] ptr4;
delete[] ptr5;
}
注意:
😊 申请和释放单个元素的空间,使用new 和delete操作符.
😍 申请和释放连续的空间,使用new[] 和delete[],注意:匹配起来使用.
同时我们通过new创建链表也会更方便:
cpp
struct ListNode
{
int val;
ListNode* next;
ListNode(int x)
:val(x)
,next(nullptr)
{}
};
int main()
{
ListNode* n1 = new ListNode(1);
ListNode* n2 = new ListNode(2);
ListNode* n3 = new ListNode(3);
ListNode* n4 = new ListNode(4);
n1->next = n2;
n2->next = n3;
n3->next = n4;
n4->next = nullptr;
ListNode* cur = n1;
while (cur != nullptr)
{
cout << cur->val;
if (cur->next != nullptr)
cout << "->";
cur = cur->next;
}
cout << endl;
ListNode* tmp = nullptr;
cur = n1;
while (cur != nullptr)
{
tmp = cur;
cur = cur->next;
delete tmp;
}
return 0;
}
二、new和delete操作自定义类型
cpp
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;
}
😉总结一下:
C++中的new和delete 不仅在用法上跟C中的malloc和free 有所差别,其实最大的差别是new会调用构造函数,delete会调用析构函数。
三、C++异常的捕获和处理
异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或间接的调用者处理这个错误。
**throw:**当问题出现时,程序会抛出一个异常(可以抛任意类型的异常)。这是通过使用throw 关键字来完成的。
**catch:**在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常,可以有多个catch进行捕获。
**try:**try块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch 块。
cpp
try
{
// 保护的标识代码
}
catch( ExceptionName e1 )
{
// catch 块
}
catch( ExceptionName e2 )
{
// catch 块
}
catch( ExceptionName eN )
{
// catch 块
}
代码示例:
cpp
double Division(int len, int time)
{
if (time == 0)
{
throw "除0错误";
}
else
{
return (double)len / (double)time;
}
}
//多个try catch 会优先跳近的,但是前提是近的类型是匹配的 如果不匹配还是会优先调匹配的 所以优先级1、类型。2、就近
void Func()
{
try {
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
}
catch (const char s) //如果在该位置捕获,那么后面的语句还是会正常去调用
{
cout << "Func()" << s << endl;
}
}
int main()
{
try
{
Func();
}
catch (const char* str)
{
cout << "main()" << str << endl;
}
return 0;
}
😇C++ 异常的缺点:
-
异常会导致程序的执行流乱跳,并且非常的混乱,并且是运行时出错抛异常就会乱跳。这会导致我们跟踪调试时以及分析程序时比较困难(主要问题)。
-
异常会有一些性能的开销。当然在现代硬件速度很快的情况下,这个影响基本忽略不计。
-
C++没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常安全问题。这个需要使用RAII来处理资源的管理问题。学习成本较高。
-
C++标准库的异常体系定义得不好,导致大家各自定义各自的异常体系,非常的混乱。
-
异常尽量规范使用,否则后果不堪设想,随意抛异常,外层捕获的用户苦不堪言。
所以异常规范有两点:
一、工程实践中,建议自定义异常统一继承自 std::exception(或其派生类),便于统一捕获和处理;
二、C++11 及以上,函数 "是否抛出异常" 通过 noexcept 规范,"抛什么异常" 已无合法的语法级规范(仅可通过文档说明)。
4. operator new与operator delete函数
😇new 和delete 是用户进行动态内存申请和释放的操作符,而operator new 和operator delete是系统提供的全局函数。
😘new 在底层调用operator new 全局函数来申请空间,delete 在底层通过operator delete全局函数来释放空间。
operator new:
-
该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;
-
申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
cpp
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来释放空间的。
cpp
void operator delete(void *pUserData)
{
_CrtMemBlockHeader * pHead; // Debug模式内存块的头部结构(存储内存块信息)
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0)); // RTC(运行时检查)钩子,调试用
if (pUserData == NULL) // 空指针保护:释放NULL是安全的,直接返回
return;
_mlock(_HEAP_LOCK); /* 线程锁:阻塞其他线程,保证堆操作线程安全 */
__TRY // MSVC异常保护块(类似 try)
{
pHead = pHdr(pUserData); // 从用户指针转换到内存块头部(Debug模式内存块有额外头部)
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
// 断言校验:确保内存块类型合法(Debug下触发)
_free_dbg( pUserData, pHead->nBlockUse );
// Debug版释放函数:记录内存释放、检测内存泄漏
}
__FINALLY // 类似 finally,保证锁一定会释放
{
_munlock(_HEAP_LOCK); /* 释放线程锁:无论是否抛异常,都解锁 */
}
__END_TRY_FINALLY
return;
}
通过上述两个全局函数的实现知道:
😊operator new实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。
😚operator delete 最终是通过free来释放空间的。
5. new和delete的实现原理
一、内置类型
如果申请的是内置类型的空间,new和malloc,delete和free基本类似。
不同的地方是: new / delete 申请和释放的是单个元素 的空间,new[] / delete[] 申请的是连续空间 ,而且new在申请空间失败时会抛异常,malloc会返回NULL。
二、自定义类型
😋 new的原理
-
调用operator new函数申请空间。
-
在申请的空间上执行构造函数,完成对象的构造。
😜 delete的原理
-
在空间上执行析构函数,完成对象中资源的清理工作。
-
调用operator delete函数释放对象的空间。
🤪 new [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申请的空间不会初始化,new可以初始化。
-
malloc 申请空间时,需要手动计算空间大小并传递 ,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可。
-
malloc的返回值为void* , 在使用时必须强转,new不需要,因为new后跟的是空间的类型。
-
malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常。
-
申请自定义类型对象 时,malloc / free只会开辟空间,不会调用构造函数与析构函数 ,而new 在申请空间后会调用构造函数 完成对象的初始化,delete 在释放空间前会调用析构函数完成空间中资源的清理释放。
💯如果这篇文章对你有用的话,请继续关注!