C/C++ 内存管理深度解析:从内存分布到实践应用(malloc和new,free和delete的对比与使用,定位 new )
一、引言:理解内存管理的核心价值
在系统级编程领域,内存管理是决定程序性能、稳定性和安全性的关键因素。C/C++ 作为底层开发的主流语言,赋予开发者直接操作内存的能力,却也要求开发者深入理解内存布局与生命周期管理。本文将从内存分布原理出发,对比 C/C++ 内存管理机制,解析核心接口的实现逻辑与最佳实践,帮助开发者建立系统化的内存管理认知。
二、C/C++ 内存分布:程序运行的空间蓝图
1. 内核空间
- 特性:用户代码无法直接读写,属于操作系统内核使用的内存区域,用于存放内核程序相关数据,与用户程序隔离。
2. 栈区(Stack)
- 存储内容:局部变量(如
Test
函数中的localVar
)、函数形式参数、函数调用现场保护信息等。 - 管理方式:由编译器自动分配和释放,遵循 “后进先出” 原则(类似栈数据结构)。函数调用时,参数和局部变量依次入栈;函数执行结束,内存自动回收。
- 特点:内存分配效率高,但空间有限。若函数内局部变量过多,可能引发栈溢出错误。
3. 内存映射段
- 存储内容:用于文件映射(如
mmap
操作)、动态链接库加载、匿名内存映射等。 - 作用:实现文件数据与内存的映射,或加载动态库供程序调用,提升数据访问效率。
4. 堆区(Heap)
- 存储内容:通过
malloc
、calloc
、realloc
等函数动态申请的内存(如图中ptr1
、ptr2
、ptr3
指向的空间)。 - 管理方式:由程序员手动分配和释放(需调用
free
)。空间大小灵活,可动态调整。 - 特点:若分配后未释放(如遗漏
free(ptr1)
),会导致内存泄漏;内存分配和释放的开销相对较大。
5. 数据段
- 存储内容:
- 全局变量:如
globalVar
。 - 静态变量:包括全局静态变量(
staticGlobalVar
)和局部静态变量(Test
函数中的staticVar
)。
- 全局变量:如
- 特点:程序运行期间持续占用内存直到结束,初始化数据存储在此区域,分为初始化数据段(已赋值变量)和未初始化数据段(未赋值全局 / 静态变量,又称 BSS 段)。
6. 代码段
- 存储内容:可执行的代码指令、只读常量(如字符串常量
"abcd"
)。 - 特点:内容只读,程序运行时不能修改,用于存放编译后的机器码,确保代码执行的稳定性。
通过这种划分,C/C++ 程序实现了内存的高效管理,不同区域各司其职,既保证了程序运行效率,也对内存使用的安全性和可控性提供了支持。
三、C 语言动态内存管理:手动控制的艺术
3.1 核心函数解析(->malloc,realloc,free详细讲解
)
C 语言通过 4 个核心函数实现堆内存管理,每个函数的设计哲学与使用场景各有不同:
(1)malloc(size)
:基础内存申请
- 特性:申请
size
字节未初始化内存,失败返回NULL
- 用法:
void* malloc(size_t size);
- 注意:返回值需强转类型,申请后需手动初始化
int* ptr = (int*)malloc(4); // 申请4字节(1个int),值为随机值
*ptr = 10; // 手动赋值初始化
(2)malloc(n, size)
:批量初始化内存
- 特性:申请
n*size
字节内存,初始化为 0,失败返回NULL
- 优势:避免未初始化内存的脏数据问题
- 用法:
void* calloc(size_t n, size_t size);
int* arr = (int*)calloc(10, sizeof(int)); // 10个int初始化为0
(3)realloc(ptr, new_size)
:动态调整内存大小
- 特性:调整
ptr
指向的内存大小,可能移动内存地址 - 返回值:新地址(原地址可能失效),失败返回
NULL
(原指针仍有效) - 安全用法:
int* oldPtr = ptr;
ptr = (int*)realloc(ptr, new_size);
if (!ptr) { // 失败时恢复旧指针ptr = oldPtr;// 处理错误
}
(4)free(ptr)
:释放堆内存
- 规则:仅能释放
malloc/calloc/realloc
返回的指针 - 禁忌:释放非堆内存(如栈指针)、重复释放、释放后使用指针(野指针)
3.2 面试高频问题:三函数对比
函数 | 初始化行为 | 参数含义 | 内存对齐 | 失败处理 |
---|---|---|---|---|
malloc | 不初始化 | 单一内存大小 | 自然对齐 | 返回NULL |
calloc | 初始化为 0 | 元素个数 + 单元素大小 | 严格对齐 | 返回NULL |
realloc | 不初始化 | 原指针 + 新大小 | 可能调整对齐 | 返回NULL (原指针可能失效) |
四、C++ 内存管理进化:面向对象的内存哲学
C++ 在 C 的基础上引入new/delete
操作符,针对自定义类型实现了 “构造 - 使用 - 析构” 的完整生命周期管理。
4.1 操作符基础:内置类型的便捷管理
(1)单个对象操作
int* ptr1 = new int;
- 功能:这行代码使用
new
操作符为一个int
类型的对象动态分配内存。new int
会在堆上分配一块大小为sizeof(int)
字节的内存空间,一般在常见的系统中sizeof(int)
为 4 字节,这和malloc(4)
的作用类似,都是申请一块 4 字节的内存区域。 - 初始化情况:这里分配的内存并没有被初始化,也就是说这块内存中的值是未定义的,可能包含任意的垃圾值。
- 内存释放:当不再需要这块内存时,需要使用
delete
操作符来释放它。delete ptr1;
会将ptr1
所指向的内存归还给系统。
int* ptr2 = new int(10);
- 功能:同样是使用
new
操作符为一个int
类型的对象动态分配内存,不过这里在分配内存的同时进行了值初始化。 - 初始化情况:括号中的
10
表示将新分配的int
对象初始化为10
。这种方式确保了新对象有一个明确的初始值。 - 内存释放:和
ptr1
一样,当不再需要这块内存时,使用delete ptr2;
来释放它。
(2)数组对象操作
int* arr1 = new int[5];
- 功能:这行代码使用
new
操作符为一个包含 5 个int
类型元素的数组动态分配内存。new int[5]
会在堆上分配一块大小为5 * sizeof(int)
字节的连续内存空间。 - 初始化情况:这里分配的数组元素并没有被初始化,也就是说数组中的每个元素的值都是未定义的,可能包含任意的垃圾值。
- 内存释放:当不再需要这个数组时,需要使用
delete[]
操作符来释放它。delete[] arr1;
会确保数组中的每个元素所占用的内存都被正确释放。如果使用delete arr1;
而不是delete[] arr1;
,只会释放数组首元素的内存,而其余元素的内存不会被释放,从而导致内存泄漏。
int* arr2 = new int[5]{1, 2, 3, 4, 5};
- 功能:这是 C++11 引入的聚合初始化语法,同样是为一个包含 5 个
int
类型元素的数组动态分配内存,并且在分配内存的同时对数组元素进行初始化。 - 初始化情况:花括号中的值
{1, 2, 3, 4, 5}
依次对数组的每个元素进行初始化,即arr2[0]
被初始化为1
,arr2[1]
被初始化为2
,以此类推。 - 内存释放:和
arr1
一样,当不再需要这个数组时,使用delete[] arr2;
来释放它。
3. 总结
- 内置类型(像
int
、double
、char
等):使用new
和delete
时,没有构造函数和析构函数的调用,主要是进行内存的分配和释放,和malloc
与free
功能类似,但new
支持值初始化。
虽然内置类型使用 new
和 delete
与 malloc
和 free
类似,但在 C++ 中,推荐使用 new
和 delete
,因为它们更符合 C++ 的面向对象特性,并且在使用自定义类型时能自动处理构造和析构。
4.2 自定义类型的核心差异:构造与析构的介入
操作符基础:自定义类型
跟内置类型其实都差不多,但有很多需要注意的细节,避免出现类型的问题。
代码示例:
#include <iostream>
using namespace std;class A {
public:// 带默认参数的构造函数A(int a = 0): _a(a) {cout << "构造函数,参数值: " << a << endl;}~A() {cout << "析构函数,对象值: " << _a << endl;}
private:int _a;
};int main() {// 情况1: 单个对象,提供参数调用构造函数A* p2 = new A(5);// 情况2: 数组对象,使用初始化列表初始化A* p4 = new A[5]{1, 2, 3, 4, 5};// 释放内存delete p2;delete[] p4;return 0;
}
详细分析
1. 单个对象分配及构造函数匹配 (A* p2 = new A(5);
)
- 构造函数匹配:当执行
A* p2 = new A(5);
时,new
操作符首先调用operator new
为A
类型的对象分配内存。接着,会寻找匹配的构造函数。在这个例子中,类A
有一个构造函数A(int a = 0)
,传入的参数5
可以匹配该构造函数,所以会调用A(5)
进行对象初始化。 - 错误情况:如果类
A
没有能接受一个int
类型参数的构造函数,编译器会报错。例如,如果类A
只有一个无参构造函数A()
,那么new A(5)
就会因找不到匹配的构造函数而无法编译通过。 - 默认参数情况:如果构造函数有默认参数,如
A(int a = 0)
,当使用A* p2 = new A();
时,由于没有提供参数,会使用默认参数0
调用构造函数A(0)
。
2. 数组对象分配及初始化列表 (A* p4 = new A[5]{1, 2, 3, 4, 5};
)
- 初始化列表与构造函数匹配:执行
A* p4 = new A[5]{1, 2, 3, 4, 5};
时,new
操作符调用operator new[]
为包含 5 个A
类型对象的数组分配连续内存。然后,会根据初始化列表中的值依次调用构造函数来初始化每个对象。这里会依次调用A(1)
、A(2)
、A(3)
、A(4)
、A(5)
。 - 初始化列表元素不足情况:如果初始化列表中的元素个数少于数组大小,如
A* p4 = new A[5]{1, 2, 3};
,对于剩余未提供值的元素,会尝试使用默认构造函数进行初始化。如果类A
的构造函数没有默认参数(即没有A(int a = 0)
这种形式),编译器会报错,因为找不到合适的构造函数来初始化剩余元素。 - 初始化列表元素过多情况:如果初始化列表中的元素个数多于数组大小,这是不允许的,编译器会报错。因为初始化列表的元素个数必须小于等于数组的大小。
补充:也可以 A* p4 = new A[5]{A(1), A(2),A(3), A(4), A(5)};,主要对付就是有多值传参,列:A* p5 = new A[5]{A(1,2), A(2,3),A(3,4), A(4,5), A(5,6)};
这种形式,明确地调用该构造函数来初始化数组元素。
3. delete
操作符
- 单个对象释放 (
delete p2;
):delete
操作符首先调用p2
所指向对象的析构函数~A()
进行资源清理,然后调用operator delete
释放该对象占用的内存。 - 数组对象释放 (
delete[] p4;
):delete[]
操作符会依次调用数组中每个对象的析构函数,确保每个对象的资源都被正确清理,最后调用operator delete[]
释放整个数组占用的内存。如果使用delete p4;
来释放数组对象,只会调用数组首元素的析构函数,并且只释放首元素的内存,会导致内存泄漏和其他对象的资源未被清理。
总结
- 使用
new
操作符创建对象时,编译器会根据提供的参数寻找匹配的构造函数。如果找不到匹配的构造函数,会导致编译错误。 - 对于数组对象的初始化列表,元素个数应小于等于数组大小,且剩余元素需要有合适的构造函数(如默认构造函数)来进行初始化。
- 使用
delete
释放对象时,对于单个对象使用delete
,对于数组对象必须使用delete[]
,以确保正确调用析构函数和释放内存。
当管理自定义类型(如类对象)时,new/delete
与malloc/free
展现本质区别:
4.3 底层实现原理:operator new/delete 揭秘
1. operator new
等价于 malloc
+ 异常处理
功能概述
operator new
函数的主要功能是分配指定大小的内存块,这和 malloc
函数的功能类似。然而,operator new
在内存分配失败时会抛出 std::bad_alloc
异常,而 malloc
则是返回 NULL
指针。
代码对比
以下是 operator new
和 malloc
的使用示例及对比:
#include <iostream>
#include <new>
#include <cstdlib>int main() {// 使用 operator newtry {int* ptr1 = static_cast<int*>(operator new(sizeof(int)));if (ptr1) {std::cout << "operator new 分配内存成功" << std::endl;operator delete(ptr1);}} catch (const std::bad_alloc& e) {std::cout << "operator new 分配内存失败: " << e.what() << std::endl;}// 使用 mallocint* ptr2 = static_cast<int*>(std::malloc(sizeof(int)));if (ptr2) {std::cout << "malloc 分配内存成功" << std::endl;std::free(ptr2);} else {std::cout << "malloc 分配内存失败" << std::endl;}return 0;
}
详细解释
operator new
:当调用operator new
时,它会尝试分配指定大小的内存。如果分配成功,就返回指向该内存块的指针;如果分配失败,就会抛出std::bad_alloc
异常。所以,在使用operator new
时,通常需要使用try-catch
块来捕获可能的异常。malloc
:malloc
函数同样用于分配指定大小的内存。若分配成功,返回指向该内存块的指针;若分配失败,返回NULL
指针。因此,在使用malloc
时,需要检查返回值是否为NULL
来判断内存分配是否成功。
内置类型 vs 自定义类型(new/delete 核心区别)
对比点 | 内置类型(int、char 等) | 自定义类型(类 / 结构体) |
---|---|---|
构造 / 析构函数 | ❌ 没有,无需初始化 / 清理 | ✅ 有,必须通过构造函数初始化,析构函数清理资源 |
new 操作核心 | 分配内存,可直接赋值(如new int(10) ),不调用任何函数 | 先分配内存,再自动调用构造函数完成对象初始化 |
delete 操作核心 | 直接释放内存,不调用任何函数 | 先自动调用析构函数清理资源,再释放内存 |
初始化方式 | 简单值初始化(直接写值在括号里) | 必须通过构造函数(无参 / 有参),数组需默认构造函数 |
数组释放风险 | 用错delete[] 仅内存泄漏(无析构函数) | 用错delete[] 会漏调析构函数,导致资源泄漏 + 程序错误 |
核心本质 | 简单数据,只需要内存和值 | 复杂逻辑 / 资源,需要构造 / 析构函数管理生命周期 |
一句话总结:
- 内置类型:
new
/delete
直接操作内存,像 “裸奔”,简单赋值即可,无需复杂初始化。 - 自定义类型:
new
/delete
必须通过构造 / 析构函数 “穿脱衣服”,管理对象的 “出生” 和 “死亡”,确保资源正确使用和释放。
2. operator delete
等价于 free
功能概述
operator delete
函数的主要功能是释放之前由 operator new
分配的内存块,这和 free
函数的功能类似。二者都只是单纯地释放内存,不会调用对象的析构函数。
代码对比
以下是 operator delete
和 free
的使用示例及对比:
#include <iostream>
#include <cstdlib>int main() {// 使用 operator new 和 operator deleteint* ptr1 = static_cast<int*>(operator new(sizeof(int)));if (ptr1) {std::cout << "operator new 分配内存成功" << std::endl;operator delete(ptr1);std::cout << "operator delete 释放内存成功" << std::endl;}// 使用 malloc 和 freeint* ptr2 = static_cast<int*>(std::malloc(sizeof(int)));if (ptr2) {std::cout << "malloc 分配内存成功" << std::endl;std::free(ptr2);std::cout << "free 释放内存成功" << std::endl;}return 0;
}
详细解释
operator delete
:operator delete
函数接收一个指向之前由operator new
分配的内存块的指针,然后将该内存块归还给系统。free
:free
函数接收一个指向之前由malloc
、calloc
或realloc
分配的内存块的指针,然后将该内存块归还给系统。
3. 总结
operator new
和malloc
:二者的主要功能都是分配内存,但operator new
在内存分配失败时会抛出异常,而malloc
则返回NULL
指针。operator delete
和free
:二者的主要功能都是释放内存,它们的行为基本一致。
-
自定义类型流程:
2. new
操作符的工作流程
new
操作符的主要作用是完成两个关键任务:一是分配内存,二是调用对象的构造函数。它的具体工作步骤如下:
- 调用
operator new
分配内存:new
操作符首先会调用operator new
函数,该函数的作用是从系统中请求一块足够大小的内存空间。operator new
函数只是单纯地进行内存分配,不会调用对象的构造函数。 - 调用对象的构造函数:在成功分配内存之后,
new
操作符会在这块新分配的内存上调用对象的构造函数,从而完成对象的初始化工作。
3. delete
操作符的工作流程
delete
操作符的主要作用是完成两个关键任务:一是调用对象的析构函数,二是释放对象所占用的内存。它的具体工作步骤如下:
- 调用对象的析构函数:
delete
操作符首先会调用对象的析构函数,该函数的作用是清理对象所占用的资源,例如释放动态分配的内存、关闭文件等。 - 调用
operator delete
释放内存:在对象的析构函数执行完毕之后,delete
操作符会调用operator delete
函数,该函数的作用是将对象所占用的内存归还给系统。
4. 总结
- 分工明确:
operator new
和operator delete
主要负责内存的分配和释放,它们不涉及对象的构造和析构。而new
和delete
操作符则是更高级别的抽象,它们不仅会调用operator new
和operator delete
来处理内存,还会自动调用对象的构造函数和析构函数,确保对象的正确初始化和清理。 - 可定制性:
operator new
和operator delete
可以被重载,从而实现自定义的内存管理策略。而new
和delete
操作符的行为是固定的,它们会按照上述流程来调用相应的函数。
五、定位 new 表达式(placement-new):内存池的黄金搭档
1. 定位 new 表达式概述
定位 new
表达式(placement-new)是 C++ 中一个特殊的内存分配和对象初始化机制。它允许你在已经分配好的内存块上构造对象,而不是让 new
操作符去分配新的内存。这种机制在一些特定场景下非常有用,尤其是在实现内存池技术时。
2. 语法详解
定位 new
表达式的语法如下:
new(内存地址) 类型(构造参数);
- 内存地址:这是一个已经分配好的内存块的地址,可以是从堆、栈或者静态内存中获取的。定位
new
会在这个地址上构造对象,而不会去重新分配内存。 - 类型:要构造的对象的类型。
- 构造参数:传递给对象构造函数的参数,用于初始化对象。
3. 作用及原理
作用
定位 new
的主要作用是在已有的内存上显式地调用构造函数来初始化对象。这在一些需要精细控制内存分配和对象生命周期的场景中非常有用,例如内存池技术。
原理
正常的 new
操作符会完成两个步骤:首先调用 operator new
函数分配内存,然后在这块新分配的内存上调用对象的构造函数。而定位 new
跳过了内存分配的步骤,直接在指定的内存地址上调用对象的构造函数。
4. 示例代码
#include <iostream>// 自定义类
class MyClass {
public:MyClass(int value) : data(value) {std::cout << "MyClass 构造函数被调用,data = " << data << std::endl;}~MyClass() {std::cout << "MyClass 析构函数被调用,data = " << data << std::endl;}void printData() {std::cout << "data = " << data << std::endl;}
private:int data;
};int main() {// 步骤1: 手动分配内存void* rawMemory = operator new(sizeof(MyClass));// 步骤2: 使用定位 new 在已分配的内存上构造对象MyClass* obj = new(rawMemory) MyClass(10);// 步骤3: 使用对象obj->printData();// 步骤4: 手动调用析构函数obj->~MyClass();// 步骤5: 释放内存operator delete(rawMemory);return 0;
}
5. 代码解释
步骤 1: 手动分配内存
void* rawMemory = operator new(sizeof(MyClass));
这里使用 operator new
函数手动分配了一块大小为 sizeof(MyClass)
的内存。operator new
只是分配内存,不会调用对象的构造函数。
步骤 2: 使用定位 new 在已分配的内存上构造对象
MyClass* obj = new(rawMemory) MyClass(10);
使用定位 new
表达式在 rawMemory
所指向的内存地址上构造了一个 MyClass
对象,并传递参数 10
给构造函数。
步骤 3: 使用对象
obj->printData();
调用对象的成员函数,使用对象的功能。
步骤 4: 手动调用析构函数
obj->~MyClass();
由于定位 new
没有自动调用析构函数的机制,所以需要手动调用析构函数来清理对象的资源。
步骤 5: 释放内存
operator delete(rawMemory);
使用 operator delete
函数释放之前分配的内存。
6. 内存池技术中的应用
内存池技术是一种预先分配大块内存,然后按需初始化对象的技术。定位 new
在内存池技术中非常有用,因为它可以在内存池预先分配的内存块上构造对象,避免了频繁的内存分配和释放带来的开销。
以下是一个简单的内存池示例:
#include <iostream>
#include <vector>// 自定义类
class MyClass {
public:MyClass(int value) : data(value) {std::cout << "MyClass 构造函数被调用,data = " << data << std::endl;}~MyClass() {std::cout << "MyClass 析构函数被调用,data = " << data << std::endl;}void printData() {std::cout << "data = " << data << std::endl;}
private:int data;
};// 简单的内存池类
class MemoryPool {
public:MemoryPool(size_t blockSize, size_t numBlocks) {for (size_t i = 0; i < numBlocks; ++i) {void* block = operator new(blockSize);freeBlocks.push_back(block);}}~MemoryPool() {for (void* block : freeBlocks) {operator delete(block);}}void* allocate() {if (freeBlocks.empty()) {return nullptr;}void* block = freeBlocks.back();freeBlocks.pop_back();return block;}void deallocate(void* block) {freeBlocks.push_back(block);}
private:std::vector<void*> freeBlocks;
};int main() {// 创建内存池MemoryPool pool(sizeof(MyClass), 5);// 从内存池分配内存void* rawMemory = pool.allocate();// 使用定位 new 在内存池分配的内存上构造对象MyClass* obj = new(rawMemory) MyClass(20);// 使用对象obj->printData();// 手动调用析构函数obj->~MyClass();// 将内存块返回给内存池pool.deallocate(rawMemory);return 0;
}
7. 内存池示例解释
- MemoryPool 类:实现了一个简单的内存池,预先分配了多个大小为
sizeof(MyClass)
的内存块,并将它们存储在freeBlocks
向量中。 - allocate 方法:从内存池中取出一个空闲的内存块。
- deallocate 方法:将使用完的内存块返回给内存池。
- 定位
new
的使用:从内存池分配内存后,使用定位new
在这块内存上构造MyClass
对象。
通过使用定位 new
和内存池技术,可以避免频繁的内存分配和释放,提高程序的性能。
8. 注意事项
- 手动调用析构函数:使用定位
new
构造的对象不会自动调用析构函数,需要手动调用析构函数来清理对象的资源。 - 内存管理:确保正确管理内存,避免内存泄漏。在释放对象时,先调用析构函数,再释放内存。
六、核心对比与最佳实践
6.1 malloc/free vs new/delete 深度对比
特性 | malloc/free | new/delete |
---|---|---|
接口本质 | C 标准库函数(需包含<stdlib.h> ) | C++ 操作符(关键字) |
类型安全 | 返回void* ,需强制类型转换 | 自动推导类型,无需强转(int* ptr = new int; ) |
初始化 | 不初始化,内存含随机值 | 支持值初始化(new T(value) )和默认初始化 |
数组管理 | 需手动计算总大小(n*sizeof(T) ) | new[] 自动计算元素个数,delete[] 匹配释放 |
自定义类型 | 不调用构造 / 析构函数,需手动管理资源 | 自动调用构造 / 析构函数,确保资源正确生命周期 |
错误处理 | 失败返回NULL ,需显式判空检查 | 失败抛出bad_alloc 异常,需异常处理 |
6.2 最佳实践指南
-
C 语言场景:
- 申请大块内存用
calloc
(自动初始化 0,避免脏数据) - 调整内存大小用
realloc
(注意保存旧指针防止丢失) - 始终检查
malloc
返回值,避免空指针解引用
- 申请大块内存用
-
C++ 场景:
- 管理自定义类型优先用
new/delete
,确保构造 / 析构正确调用 - 数组类型必须使用
new[]/delete[]
,避免内存泄漏(如delete arr;
未调用数组中每个对象的析构函数) - 现代 C++ 推荐使用智能指针(
unique_ptr
/shared_ptr
),自动管理内存释放,避免手动delete
- 管理自定义类型优先用
-
通用原则:
- 内存分配与释放严格配对(
malloc
→free
,new
→delete
) - 释放后立即置空指针(
ptr = nullptr;
),避免野指针 - 自定义类型中遵循 “资源获取即初始化”(RAII)原则,通过类管理资源生命周期
- 内存分配与释放严格配对(
七、常见问题与陷阱解析
7.1 内存泄漏场景
- 忘记调用
free/delete
:动态分配的内存未释放,程序长期运行导致内存耗尽 realloc
失败未处理:原指针被覆盖前未保存,导致旧内存无法释放delete[]
遗漏[]
:仅释放数组首地址,后续对象未调用析构函数(自定义类型致命错误)
7.2 野指针与悬垂指针
- 野指针:未初始化的指针(如
int* ptr; *ptr = 10;
),解引用导致未定义行为 - 悬垂指针:指向已释放内存的指针(如
free(ptr); *ptr = 10;
),访问非法内存
7.3 内存对齐问题
calloc
比malloc
提供更严格的内存对齐(适用于结构体包含对齐要求高的成员)- C++ 的
new
确保分配的内存满足目标类型的对齐要求
八、总结:从手动控制到智能管理的进化
C/C++ 内存管理的发展历程,本质是 “效率” 与 “安全” 的平衡艺术:
- C 语言提供原始但高效的手动管理接口,要求开发者精通内存布局与生命周期
- **C++** 通过
new/delete
引入面向对象的管理机制,确保自定义类型的资源正确管理 - 现代实践结合智能指针(如
std::unique_ptr
)与 RAII 模式,在保持效率的同时大幅降低出错概率
理解内存管理的核心在于掌握 “在哪里分配”(内存区域特性)、“如何正确初始化与释放”(接口匹配)、“如何处理自定义类型资源”(构造析构调用)。无论是系统内核开发还是高性能服务构建,扎实的内存管理功底都是写出健壮代码的基石。
// 终极最佳实践:现代C++智能指针替代手动管理
#include <memory>
int main() {auto ptr = std::make_unique<int>(10); // 自动管理int对象auto arr = std::make_unique<int[]>(5); // 自动管理数组// 无需手动delete,超出作用域自动释放return 0;
}
通过深入理解内存管理原理,开发者能够更精准地诊断内存泄漏、野指针等问题,在享受 C/C++ 高性能优势的同时,构建更安全可靠的系统级软件。
相关文章:
C/C++ 内存管理深度解析:从内存分布到实践应用(malloc和new,free和delete的对比与使用,定位 new )
一、引言:理解内存管理的核心价值 在系统级编程领域,内存管理是决定程序性能、稳定性和安全性的关键因素。C/C 作为底层开发的主流语言,赋予开发者直接操作内存的能力,却也要求开发者深入理解内存布局与生命周期管理。本文将从内…...
如何使用主机名在 CMD 中查找 IP 地址?
在网络中,每个系统都有一个由几位数字组成的唯一标识,称为 IP 地址。然而,记住它们可能是一项艰巨的任务,尤其是当系统数量众多时。例如,互联网上运行的每个网站都有一个 IP 地址,以便其他系统在需要时可以调用它们,但你认为记住我们访问的每个网站的长串数字是可行的吗…...
解读RTOS:第二篇 · 线程/任务管理与调度策略
1. 引言 在 RTOS 中,线程(Task)是最基本的执行单元,它封装了应用功能、资源使用和优先级属性。任务管理与调度策略决定了系统在多任务场景下的响应速度、资源分配效率与实时性保证。理解并掌握任务创建、状态转换、优先级设计和调度算法,是 RTOS 应用开发的核心内容。 2…...
linux下minio的进程管理脚本
准备工作: 参考链接: Deploy MinIO: Single-Node Single-Drive — MinIO Object Storage for Linux 下载: wget https://dl.min.io/server/minio/release/linux-amd64/minio kill-app.sh #!/bin/bash # 文件名: kill-app.sh…...
论文学习_A Survey of Binary Code Similarity
摘要:二进制代码相似性方法的主要目的是比较两个或多个二进制代码片段,以识别它们之间的相似性与差异(研究背景)。由于在许多实际场景中源代码往往不可获取,因此具备比较二进制代码的能力显得尤为重要,例如…...
python标准库--sys - 系统相关功能在算法比赛的应用
目录 1. 快速输入输出 2. 调整递归深度限制 1. 快速输入输出 算法比赛中,大量数据的读写可能成为瓶颈。sys.stdin和sys.stdout比内置的input()和print()效率更高。 import sys# 读取多行输入(每行一个整数) n int(sys.stdin.readline()) …...
运算放大器相关的电路
1运算放大器介绍 解释:运算放大器本质就是一个放大倍数很大的元件,就如上图公式所示 Vp和Vn相差很小但是放大后输出还是会很大。 运算放大器不止上面的三个引脚,他需要独立供电; 如图比较器: 解释:Vp&…...
进程和线程
目录 1. 基本定义 2. 核心区别 3. 优缺点对比 进程和线程是操作系统中用于实现并发执行的两个核心概念,它们既有相似之处,又有明显的区别。下面从多个维度对它们进行对比分析: 1. 基本定义 进程(Process) 进程是程…...
生成对抗网络(GAN)深度解析:理论、技术与应用全景
生成对抗网络(Generative Adversarial Networks,GAN)作为深度学习领域的重要突破,通过对抗训练框架实现了强大的生成能力。本文从理论起源、数学建模、网络架构、工程实现到行业应用,系统拆解GAN的核心机制,涵盖基础理…...
Java面试全记录:Spring Cloud+Kafka+Redis实战解析
Java面试全记录:Spring CloudKafkaRedis实战解析 人物设定 姓名:张伟(随机生成唯一姓名) 年龄:28岁 学历:硕士 工作年限:5年 工作内容: 基于Spring Cloud搭建微服务架构使用Kafka…...
人脸识别deepface相关笔记
人脸识别deepface相关笔记 项目地址项目结构 项目地址 https://github.com/serengil/deepface.git 项目结构...
量子加密通信:守护信息安全的未来之盾
摘要 在数字化时代,信息安全成为全球关注的焦点。传统加密技术面临着被量子计算破解的风险,而量子加密通信作为一种基于量子力学原理的新型加密技术,提供了理论上无条件安全的通信保障。本文将详细介绍量子加密通信的基本原理、技术实现、应用…...
三、transformers基础组件之Model
1. 什么是Model Head Model Head 是连接在模型后的层,通常为1个或多个全连接层Model Head 将模型的编码的表示结果进行映射,以解决不同类型的任务 不同的任务会有不同的Model Head。 2. 模型加载 2.1 在线加载 预训练模型的加载与Tokenizer类似,我们只需要指定想…...
【语法】C++的多态
目录 虚函数的重写: 虚函数 重写(覆盖) 虚函数重写的两个例外: 协变: 析构函数的重写: 练习: final和override关键字 抽象类 接口继承和实现继承 虚函数重写的原理: 打印虚函数表: …...
WebGIS开发新突破:揭秘未来地理信息系统的神秘面纱
你有没有想过,未来的地理信息系统(GIS)会是什么样子?是像电影里那样,一块透明屏幕就能呈现整个城市的实时动态?还是像《钢铁侠》中那样,一个手势就能操控全球地图? 其实,…...
JVM类加载
JVM类加载 1. 类的生命周期(类加载过程)类加载的五个阶段: 2. 类加载器的分类3. 双亲委派模型4. 类的卸载与热加载5.类加载器命名空间隔离 1. 类的生命周期(类加载过程) 类加载的五个阶段: 加载ÿ…...
AD开启交叉选择功能,只选中器件,不选中网络、焊盘
AD开启交叉选择功能,只选中器件,不选中网络、焊盘。 一、打开首选项 二、打开System→Navigationg,配置如下。 三、最后点击OK即可。...
机器学习——集成学习基础
一、鸢尾花数据训练模型 1. 使用鸢尾花数据分别训练集成模型:AdaBoost模型,Gradient Boosting模型 2. 对别两个集成模型的准确率以及报告 3. 两个模型的预测结果进行可视化 需要进行降维处理,两个图像显示在同一个坐标系中 代码展示&…...
C++匿名函数
C 中的匿名函数(Lambda 表达式)是 C11 引入的一项重要特性,它允许你在需要的地方定义一个临时的、无名的函数对象,使代码更加简洁和灵活。 1. 基本语法 Lambda 表达式的基本结构: [capture list](parameter list) -…...
互联网大厂Java面试实战:Spring Boot到微服务的技术问答解析
💪🏻 1. Python基础专栏,基础知识一网打尽,9.9元买不了吃亏,买不了上当。 Python从入门到精通 😁 2. 毕业设计专栏,毕业季咱们不慌忙,几百款毕业设计等你选。 ❤️ 3. Python爬虫专栏…...
神经网络是如何工作的
人工智能最核心的技术之一,就是神经网络(Neural Networks)。但很多初学者会觉得它是个黑盒:为什么神经网络能识别图片、翻译语言,甚至生成文章? 本文用图解最小代码实现的方式,带你深入理解&am…...
Kubernetes控制平面组件:Kubelet详解(二):核心功能层
云原生学习路线导航页(持续更新中) kubernetes学习系列快捷链接 Kubernetes架构原则和对象设计(一)Kubernetes架构原则和对象设计(二)Kubernetes架构原则和对象设计(三)Kubernetes控…...
【android bluetooth 框架分析 02】【Module详解 13】【CounterMetrics 模块介绍】
1. CounterMetrics 介绍 CounterMetrics 模块代码很少, 我简单介绍一下。 // system/gd/metrics/counter_metrics.cc #define LOG_TAG "BluetoothCounterMetrics"#include "metrics/counter_metrics.h"#include "common/bind.h" #i…...
Matlab自学笔记五十四:符号数学工具箱和符号运算、符号求解、绘图
1.什么是符号数学工具箱? 符号数学工具箱是Matlab针对符号对象的运算功能,它引入了一种特殊的数据类型 - 符号对象; 该数据类型包括符号数字,符号变量,符号表达式和符号函数,还包含符号矩阵,以…...
Matlab 模糊控制平行侧边自动泊车
1、内容简介 Matlab 233-模糊控制平行侧边自动泊车 可以交流、咨询、答疑 2、内容说明 略 3、仿真分析 略 4、参考论文 略...
新书速览|纯血鸿蒙HarmonyOS NEXT原生开发之旅
《纯血鸿蒙HarmonyOS NEXT原生开发之旅》 本书内容 《纯血鸿蒙HarmonyOS NEXT原生开发之旅》全面系统地介绍了基于HarmonyOS NEXT系统进行原生应用开发的实用技巧。全书共12章,内容涵盖从基础工具使用到高级功能实现的各个方面。第1章详细介绍了开发环境的搭建、Ar…...
tinyint(3)数据类型讲解
TINYINT(3) 是数据库中用于定义字段数据类型的一种写法,常见于 MySQL 等数据库系统。下面来详细了解其含义和作用: 数据类型本质 TINYINT 属于整数类型,在不同的数据库系统中,它所占用的存储空间和表示范围通常是固定的。以 MyS…...
manjaro系统详解
1. Manjaro 概述 Manjaro 是一款基于 Arch Linux 的滚动更新发行版,以 用户友好、易用性 和 硬件兼容性 为核心设计理念。它继承了 Arch 的灵活性和软件丰富性,同时通过图形化工具和稳定的更新策略降低了使用门槛,适合从新手到高级用户的广泛…...
# 实时英文 OCR 文字识别:从摄像头到 PyQt5 界面的实现
实时英文 OCR 文字识别:从摄像头到 PyQt5 界面的实现 引言 在数字化时代,文字识别技术(OCR)在众多领域中发挥着重要作用。无论是文档扫描、车牌识别还是实时视频流中的文字提取,OCR 技术都能提供高效且准确的解决方案…...
9.3.云原生架构模式
目录 一、云原生架构核心概念 云原生定义与核心原则 • 四大核心要素:容器化、微服务、DevOps、持续交付 • 核心原则:弹性、可观测性、自动化、不可变基础设施 云原生技术矩阵 • 容器与编排:Docker、Kubernetes、CRI-O • 服务治理&#…...
现代化水库运行管理矩阵平台如何建设?
政策背景 2023年8月24日,水利部发布的水利部关于加快构建现代化水库运行管理矩阵的指导意见中指出,在全面推进水库工程标准化管理的基础上,强化数字赋能,加快构建以推进全覆盖、全要素、全天候、全周期“四全”管理,完…...
木马查杀引擎—关键流程图
记录下近日研究的木马查杀引擎,将关键的实现流程图画下来 PHP AST通道实现 木马查杀调用逻辑 模型训练流程...
基于libevent的异步事件驱动型线程池实现
----------------------| IFoxThread | ← 抽象线程接口|----------------------|| dispatch() || start() || stop() || ... |----------^-----------|--------------------|----------------------| …...
ArcGIS+InVEST+RUSLE:水土流失模拟与流域管理的高效解决方案;水土保持专题地图制作
在全球生态与环境面临严峻挑战的当下,水土流失问题已然成为制约可持续发展的重要因素之一。水土流失不仅影响土地资源的可持续利用,还对生态环境、农业生产以及区域经济发展带来深远影响。因此,科学、精准地模拟与评估水土流失状况࿰…...
#S4U2SELF#S4U2Proxy#CVE-2021-42278/42287
#S4U2SELF Win08创建普通用户 s4u2 xwj456 可以看到普通用户是没用委托属性的 Win08手动赋予委托服务属性 setspn -A wsw/wsw.com s4u2 Win10身份验证 s4u2 xwj456 AS请求 两个勾 两个勾和include-pac记得按上(蓝色) ,发包之前把wiresh…...
利用基于LLM的概念提取和FakeCTI数据集提升网络威胁情报对抗虚假信息活动的能力
摘要 虚假新闻和虚假信息宣传活动的迅速蔓延对公众信任、政治稳定和网络安全构成了重大威胁。传统的网络威胁情报(CTI)方法依赖于域名和社交媒体账号等低级指标,很容易被频繁修改其在线基础设施的对手规避。为了解决这些局限性,我…...
uniapp|实现手机通讯录、首字母快捷导航功能、多端兼容(H5、微信小程序、APP)
基于uniapp实现带首字母快捷导航的通讯录功能,通过拼音转换库实现汉字姓名首字母提取与分类,结合uniapp的scroll-view组件与pageScrollTo API完成滚动定位交互,并引入uni-indexed-list插件优化索引栏性能。 目录 核心功能实现动态索引栏生成联系人列表渲染滚动定位联动性…...
使用PhpStudy搭建Web测试服务器
一、安装PhpStudy 从以下目录下载PhpStudy安装文件 Windows版phpstudy下载 - 小皮面板(phpstudy) (xp.cn) 安装成功之后打开如下界面 点击启动Apache 查看网站地址 在浏览器中输入localhost:88,出现如下页面就ok了 二、与Unity交互 1.配置下载文件路径,点击…...
Qt/C++面试【速通笔记九】—视图框架机制
在Qt中,QGraphicsView和QGraphicsScene是用于构建二维图形界面的核心组件。它们的设计使得开发者能够高效地管理和渲染图形项,支持丰富的用户交互,例如缩放、旋转、平移等。 1. QGraphicsScene和QGraphicsView的基本概念 QGraphicsScene QG…...
react-diff-viewer 如何实现语法高亮
前言 react-diff-viewer 是一个很好的 diff 展示库,但是也有一些坑点和不完善的地方,本文旨在描述如何在这个库中实现自定义语法高亮。 Syntax highlighting is a bit tricky when combined with diff. Here, React Diff Viewer provides a simple rend…...
Python实例题:Django搭建简易博客
目录 Python实例题 题目 1. 创建 Django 项目和应用 2. 配置项目 3. 设计模型 blog_app templates blog_app post_list.html admin.py models.py urls.py views.py blog_project urls.py 代码解释 models.py: admin.py: urls.py&…...
Kotlin 异步初始化值
在一个类初始化的时候或者方法执行的时候,总有一些值是需要的但是不是立即需要的,并且在需要的时候需要阻塞流程来等待值的计算,这时候异步的形式创建这个值是毋庸置疑最好的选择。 为了更好的创建值需要使用 Kotlin 的协程来创建࿰…...
扩展:React 项目执行 yarn eject 后的 config 目录结构详解
扩展:React 项目执行 yarn eject 后的 config 目录结构详解 什么是 yarn eject?React 项目执行 yarn eject 后的 config 目录结构详解📁 config 目录结构各文件作用详解env.jsgetHttpsConfig.jsmodules.jspaths.jswebpack.config.jswebpackDe…...
(自用)Java学习-5.8(总结,springboot)
一、MySQL 数据库 表关系 一对一、一对多、多对多关系设计外键约束与级联操作 DML 操作 INSERT INTO table VALUES(...) DELETE FROM table WHERE... UPDATE table SET colval WHERE...DQL 查询 基础查询:SELECT * FROM table WHERE...聚合函数:COUNT()…...
cursor 如何在项目内自动创建规则
在对话框内 / Generate。cursor rules 就会自动根据项目进行创建规则 文档来自:https://www.kdocs.cn/l/cp5GpLHAWc0p...
C++ 迭代器
1.用途: 像我们之前学习的容器map,vector等,如果需要遍历该怎么做呢?这些容器大部分对下标式遍历,无法像数组灵活使用,也包括增删改查,因为它们的特性,所以需要一种其他的方法。 那么迭代器就…...
基于微信小程序的城市特色旅游推荐应用的设计与实现
💗博主介绍💗:✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 温馨提示:文末有 CSDN 平台官方提供的老师 Wechat / QQ 名片 :) Java精品实战案例《700套》 2025最新毕业设计选题推荐…...
最大m子段和
问题描述解题思路伪代码代码实现复杂度分析 问题描述 给定一个有n(n>0)个整数的序列,要求其m个互不相交的子段,使得这m个子段和最大。 输入:整数序列{nums},m。 输出:最大m子段和。 对于m1的情况,即求最…...
4.MySQL全量、增量备份与恢复
1.数据备份的重要性 在企业中数据的价值至关重要,数据保障了企业业务的正常运行。因此,数据的安全性及数据的可靠性是运维的重中之重,任何数据的丢失都可能对企业产生严重的后果。通常情况下造成数据丢失的原因有如下几种: a.程…...
每日算法刷题Day4 5.12:leetcode数组4道题,用时1h
7. 704.二分查找 704. 二分查找 - 力扣(LeetCode) 思想 二分模版题 代码 c: class Solution { public:int search(vector<int>& nums, int target) {int nnums.size();int left0,rightn-1;int res-1;while(left<right){int midleft((…...