C&C++内存管理
C/C++内存分布
经典问题:



说明:1、栈又叫堆栈,非静态局部变量/函数参数/返回值等等,栈是向下增长的;
2、内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库;
3、堆用于程序运行时动态内存分配,堆是可以向上增长的;
4、数据段--存储全局数据和静态数据
5、代码段--可执行的代码/只读常量
2.C语言中动态内存管理方式

3.C++内存管理方式
1.new/delete操作内置类型

为单个元素预留存储空间并完成释放;通过new关键字创建实例并用delete关键字完成回收;为连续存储区域进行内存分配与回收;通过数组初始化符new[]生成新数组实例,并用delete[]完成回收
2.new和delete操作自定义类型

4.operator new和operator delete函数
new和delete运算符让用户能够进行动态内存申请与释放。这些运算符实际上是系统定义的成员函数,在新对象创建时会自动初始化相关属性并执行初始化操作;新用于内存分配并绑定相关资源;delete用于内存 deallocation并解除绑定关系;通过调用...新实现了内存管理功能;delete运算符利用...实现内存释放功能。
在C++编程语言中, operator new 机制实际上是借助 malloc 函数来获取内存空间的。当 malloc 成功返回时会直接分配所需内存并返回给程序。若 malloc 成功返回,则该操作完成并返回指定的内存块给调用者。然而,在内存分配过程中若遇到失败情况,则会执行用户所提供的应对策略以解决内存不足问题。若该策略被采用则继续尝试申请新内存块;反之,则会触发相应的异常处理机制以终止程序运行。
而 operator delete 操作则最终将由 free 函数来进行内存的释放工作。
5.new和delete的实现原理
1.内置类型
当为内置类型空间进行内存管理时
2.自定义类型
new的原理
1.调用operator new函数申请空间
2.在申请的空间上执行构造函数,完成对象的构造
delete的原理
1.在空间上执行析构函数,完成对象中资源的清理工作
2.调用operator delete函数释放对象的空间
new T[N]的原理
使用new函数创建一个新数组,在该新数组中内部会实际执行new函数来分配这些对象的空间。
2.在申请的空间上执行N次构造函数
delete[]的原理
1.在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
- 使用
$ operator_delete $函数来释放内存空间,在此过程中会依次调用 operator_delete 函数以清除每个对象的内存
6.定位new表达式(placement-new)
创建new实例是在预先分配好的内存空间内调用构造函数来初始化对象实例
使用格式:
new(place_address)type或者new(place_address)type(initializer-list)
请注意:place_address 必须是一个 pointer 类型的数据结构;initializer-list 是用于类型初始化的列表
通常在内存池应用中涉及new运算符的情形
7.常见的面试题
1.malloc/free和new/delete的区别
共同点是:都是从堆上申请空间,并且需要用户手动释放。
不同点是:malloc和free是函数,new和delete是操作符
malloc申请空间不会初始化,new可以初始化
当使用malloc为程序分配内存时,必须自行估算所需的空间大小并将该信息传递给函数以供分配。而new则只需在其后面紧跟所需的空间类型即可完成内存分配操作。
malloc函数返回的是void指针类型,在调用时必须进行类型转换;而new操作则无需转换类型,并且新出来的对象属于空闲内存区域。
当malloc申请空间失败时会返回NULL,在使用时必须先判空;旧的构造函数new不需要捕获异常处理,而新的构造函数仍需捕获可能发生的异常
在自定义类型对象创建过程中,malloc和free命令仅用于预留存储空间而不涉及对象构造和析构操作;相比之下,在使用new关键字时虽然也会预留存储空间但随后会自动启动对象的初始化过程;而delete操作则会在释放存储空间之前自动执行析构函数以清除相关资源
新旧删除操作相较于malloc和free在效率上略低,并且这是因为新旧删除操作在底层采用了封装的方式实现了对malloc和free的调用
2.设计一个类,该类只能在堆上创建对象
#include <iostream>
using namespace std;
//1.封死构造函数
//2.提供公有的静态方法
//3.封死拷贝构造:1.C++ delete函数 2.私有,只声明,不实现
class Heap{
public:
//static方法访问:1对象.方法 2.类名::方法
static Heap* GetHeap() {
return new Heap;
}
//1.C++11 delete函数(友元类都无法被调用)
//Heap(const Heap& h) = delete;
private:
Heap() {}
//2.私有,只声明,不实现(缺点:友元类可以调用)
Heap(const Heap& h) {}
};
int main(){
Heap *ph = Heap::GetHeap();
return 0;
}
3.设计一个类,该类只能在栈上创建对象
#include <iostream>
using namespace std;
//1.封死构造函数
//2.提供static方法产生栈上对象
class Stack{
public:
static Stack GetStack(){
//编译器优化:发现返回了一个匿名对象,直接返回
return Stack();
}
private:
Stack() {}
};
int main() {
//在类外面无法调用构造函数
//Stack *p = new Stack();
Stack s = Stack::GetStack();
return 0;
}
- 单例模式:规定了一个类只能创建一个实例。
这种模式确保了系统中该类只有一个实例存在。
它并提供了一个全局的访问点。
这些程序模块都可以通过这个实例进行数据共享。
例如,在某个服务器程序中,
其配置信息通常存储在一个特定的文件中。
这些配置数据由同一个单例对象统一读取,
然后其他服务进程可以通过这个单例对象来获取配置信息。
这种方式大大简化了复杂环境下的配置管理。
单例模式有两种实现模式:饿汉模式和懒汉模式
懒汉模式是指无论你是否实际使用它,在程序启动的时候都会自动生成一个独一无二的对象实例
在多线程和高并发场景下进行密集调用的情况下,在高性能需求存在时,则明显倾向于采用饥饿模式以规避资源争抢问题,并显著提升了系统的响应效率
#include <iostream>
using namespace std;
//饿汉(不管用不用类对象,都要提前准备好)
//1.构造函数设置为私有
//2.定义一个单例静态成员,静态成员在程序运行之前完成初始化
//3.提供一个静态方法获取单例静态成员
//4.防拷贝
class Singleton1
{
public:
//返回值引用值和指针,保证全局唯一
static Singleton1* GetInstance() {
return &_sin;
}
private:
Singleton1()
{}
//两种方式都可以:1.声明成私有,可以不实现
// 2.声明成一个delete函数
//Singleton(const Singleton& s);
Singleton1(const Singleton1& s) = delete;
static Singleton1 _sin;
};
Singleton1 Singleton1::_sin;
懒汉模式适用情况:当单例对象构建需要耗费大量时间并且占据大量内存空间的情形下适用(如安装扩展模块、启动网络协议连接、读取外部数据文件等),即便在程序运行期间可能不会调用该对象服务。即使如此,在程序开始前也需要完成初始化步骤(如配置数据库连接、加载静态资源文件),这将导致系统启动过程显得异常迟缓。因此,在这种情况下运用缓存模式更为高效
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
//懒汉
//1.构造函数私有
//2.封死拷贝构造
//3.提供静态线程安全的接口(double check,提高效率)
//4.定义一个静态单例类型指针,初始化为nullptr
//5.(可选)定义一个内部类,辅助释放单例指针
class Singleton2{
public:
static Singleton2* GetInstance() {
//提高后续线程调用接口效率
if (_sin == nullptr){
_mtx.lock();
//保证对象只能创建一次,防止内存泄露
if (_sin == nullptr){
_sin = new Singleton2;
}
_mtx.unlock();
}
return _sin;
}
//~Singleton() {
// if (_sin){
// //造成析构函数的递归调用
// delete _sin;
// _sin = nullptr;
// }
//}
//内部类
class GC{
public:
~GC() {
if (_sin){
delete _sin;
_sin = nullptr;
}
}
};
private:
Singleton2()
{}
Singleton2(const Singleton2& s);
static Singleton2 *_sin;
static mutex _mtx;
};
Singleton2* Singleton2::_sin = nullptr;
mutex Singleton2::_mtx;
5.内存泄漏
概念:内存泄漏现象指的是应用程序在分配特定内存空间后因设计缺陷未能对该内存空间实现有效的管理控制而导致该内存空间无法被正确释放的情况。该现象并非指实际存在的内存物理消失,而是指应用层面出现了资源浪费的问题
内存泄露分类
堆内存泄漏:在程序运行过程中,根据需求动态分配的内存块通常来自malloc、calloc、realloc或new等关键字操作(这些操作均属于动态内存管理的一部分)。每当这些内存块被使用完毕后,必须通过调用对应的free或delete函数来进行正确释放。然而,在某些情况下(如设计缺陷导致),这些未被释放的内存区域可能会永远保留下来;这会导致这些区域永远无法重新利用而形成Heap Leak现象。
系统资源泄漏:表示程序未正确释放操作系统提供的各种资源(如套接字、文件描述符及管道等),即这些资源没有被相应的函数及时释放掉而导致的巨大浪费现象;这种状况可能会造成系统的性能显著下降甚至不可逆转;严重的情况下则可能影响整个系统的稳定运行
如何检测内存泄漏------链接
在Linux环境下进行内存泄漏检测:提供一些相关的内存泄漏检测工具
适用于Windows环境中的第三方工具的操作流程
3.其他工具:内存泄漏工具比较
如何避免内存泄漏
1.工程前期良好的设计规范,养成良好的编程规范
2.事前预防,使用用智能指针来管理资源
3.如果出现问题,使用内存泄露工具检测
6.如何一次在堆上申请4G的内存
//将程序编译成x64的进程,运行下面的程序
#include <iostream>
using namespace std;
int main() {
void *p = new char[0xffffffff];
cout << "new:" << p << endl;
return 0;
}
