• 动态内存管理


    动态内存管理

    1.1各种的变量、常量的存放位置

    我们在编码的时候总会定义在各种地方 定义各种各样的常量、变量,那么我们清楚它们是存在哪里的吗?

    来看下面这段代码

    #include
    using std::cout;
    using std::endl;
    using std::exception;
    //定义全局的变量 包括全局静态变量
    int aa;
    const int ad=0;
    static int bb;
    int main1()
    {
        //各种局部变量的定义
    	int a = 0;
    	const int b = 0;
    	static int c = 0;
    	char arr[] = "abd";
    	const char* p = "gneij";
    	char* p1 = (char*)malloc(sizeof(char) * 10);
    	//打印他们的地址
    	cout << "各个变量的地址如下:" << endl;
    	cout << "aa:" << &aa << endl;
    	cout << "ad:" << &ad << endl;
    	cout << "bb:" << &bb << endl;
    	cout << "c:" << &c << endl;
    	cout << "p:" << (void*)p << endl;
    	cout << endl;
    	cout << "a:" << &a << endl;
    	cout << "b:" << &b << endl;
    	cout << "arr:" << (void*)arr << endl;
    	
    	cout << endl;
    	cout << "p1:" << (void*)p1<<endl;
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    运行结果如下:
    在这里插入图片描述

    **通过运行结果可以观察到 aa bb c 的地址是相近的 说明他们是存在同一个区域的 **

    ad p 的地址是相近的 说明是放在同一块区域的

    a b arr 的地址相近 它们也是存在同一个区域的

    p1独自存在一个区域

    • 其中 aa bb c 中aa是全局变量 bb是全局的静态变量 c是局部的静态变量 全局的普通或者静态变量是存放在 静态区(数据段)

    • ad 是全局的常量 p打印出的地址其实是常量字符串“gneij"的地址 可见全局常量和常量字符/字符串是存放在常量区(代码段)

    • a b是局部变量 arr是指针变量指向栈上的数组的数据 都是存在栈上

    • p1虽然是个指针 是指向malloc在堆上开辟的空间 故p1指向的空间堆上

    1.2c/c++内存分布

    从上面的内容知道语言的内存包括 常量区 静态区 栈区 堆区 等

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SKVvBa8Z-1658156510190)(C:\Users\华哥\AppData\Roaming\Typora\typora-user-images\image-20220718210024436.png)]

    2.1c++的动态内存管理方式

    2.1.1c++中用new和delete操作符进行动态内存的管理

    c++引入了类 类中可以包含各种类型 包括内置类型还有自定义类型 这时还用c的动态内存管理方式管理起来就很不方便 例如一个类有多个一个指针变量指向多块空间 在c中就得一一用free释放 但是c++有析构函数 可以直接全部在析构函数里free每块空间 然后调用析构函数即可。

    • 所以c++有自己的动态内存管理方式 就是new 和 delete 操作符

    new 用来创建对象(包含开辟空间) delete用来清理对象资源释放空间

    • 其中new一块单独的空间就是直接 type p=new type(初始化参数)*

    • 如果是new一块连续的空间就是 type p=new type[n];*

    class Stack
    {
    public:
    	Stack(int size = 4)
    	{
    		cout << "Stack(int size=4)" << endl;
    		_top = 0;
    		_capacity = size;
    		_a = new int[size];
    	}
    	~Stack()
    	{
    		cout << "~Stack()" << endl;
    		delete[] _a;
    		_top = _capacity = 0;
    	}
    	void print()
    	{
    		cout << "void print()" << endl;
    	}
    
    private:
    	int* _a;
    	int _top;
    	int _capacity;
    
    
    };
    
    int main()
    {
        Stack* st=new Stack(10);
        Stack* p=new Stack[10];
        delete st;
        delste[] p;
        
        return 0;
    }
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    上述代码执行如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ll2Zib8M-1658156510192)(C:\Users\华哥\AppData\Roaming\Typora\typora-user-images\image-20220718212848705.png)]

    注意:上述代码中的Stack类的构造函数和析构函数里都有输出标志 代表被调用了

    我们new 一个Stack对象的时候 发现会输出Stack的构造函数被调用的标志

    delete一个对象的时候 发现会输出Stack的析构函数被调用的标志

    可以得出结论new创建对象的时候不仅仅是会开辟空间 还会调用类的构造函数初始化对象 delete的时候会调用类的析构函清理资源 后释放空间

    深入剖析:

    其实new是由函数operator new 函数和 定位new 组合成的

    • 其中operator new 又是封装malloc 得到的 它不仅包括malloc开辟空间的功能 还会开辟空间失败后抛异常 而不是跟malloc一样返回空指针

    • 定位new则是负责显式地调用构造函数 初始化创建的对象

    注意:operator new ()是一个函数 不是运算符重载!!

    delete在底层通过operator delete全局
    函数来释放空间 但是调用operator delete前会调用析构函数清理资源

    • 与operator new()对应的 还有operator delete()也只是函数 不是重载!

    定位new的格式

    //定位new的格式
    //new(空间的地址)type(传参初始化)
    //例如:
    Stack* st= operator new(sizeof(Stack));//通过operator new 函数开辟空间 
    new(st1)Stack(4);//定位new 初始化对象  4是构造函数的参数
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    定位new的使用场景

    定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义
    类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化

    • 引申:连续的new delete 效率不高因为得向操作系统申请 可以用池化技术来解决这个问题 就是用内存池技术 使得每次不是向操作系统要空间 而是在自己的池中开辟空间 这就会大大提高效率 就像是属于自己的东西拿来用很方便 别人东西借来用就不是很方便了 operator new和operator delete可以重载 那么就可以自己写一个 用内存池来实现!!!!
    2.1.2c中用malloc、calloc、realloc、free实现动态内存的管理
    void* malloc (size_t size);
    void* calloc (size_t num, size_t size);
    void* realloc (void* ptr, size_t size);
    
    • 1
    • 2
    • 3
    • malloc 是向操作系统申请在堆上开辟所给定的以字节为单位的空间 若申请成功就返回指向该空间的地址 否则返回空指针

    • calloc也是开辟空间 其比malloc多一个参数 num是开辟的类型的个数 size是类型的大小 如开辟10个int类型的空间就是

    int* p=(int*)calloc(10,sizeof(int));
    
    • 1
    • 值得注意的是 calloc相比malloc不仅是多了个参数 而且calloc还会将开辟的空间默认初始化为0 这是malloc所不具有的!

    • realloc 是在给定的空间的后面继续开辟空间 其中第一个参数是已经开辟的空间的地址 第二个参数但是要继续开辟的空间的额大小

    free:当我们不再使用开辟的空间时就可以用free(空间地址)的 方式释放空间,可以再将指针置空更为保险

    2.1.3 二者动态内存管理的区别

    new delete 和 malloc free的区别:

    1.new 和 delete是操作符 malloc和free是函数
    2.new 会对开辟的空间进行初始化 但是malloc不会
    3.malloc开辟空间的时候需要用sizeof类型的大小 麻烦 new就不需要 后面直接跟类型即可 如果是多个对象就加一个[]即可
    4.malloc的返回类型是void 开辟好后需要强制类型转化 但是new 不需要
    5.malloc开辟空间失败返回的是空指针需要人为判断是否为空指针进而知道空间是否开辟成功 而new则不用 如果new开辟空间
    失败就会抛异常 只需要我们捕获异常即可
    6.new会自动调用开辟对象的构造函数进行初始化 delete会在释放空间前调用对象的析构函数清理资源 再释放空间 malloc free 则不会

    3.1什么是内存泄露

    内存泄露是内存丢了还是指针丢了?

    • 这个问题要知道 内存泄露是指针丢了 而不是内存丢了 内存是不会丢的

    • 如果我们开辟空间后没有释放 使得该块空间的地址丢失 就无法通过该地址去释放空间 就会使得这块空间一直被占用没有还给操作系统就会使得 操作系统的内存越来越少 长时间运行的程序 进程 如果由内存泄露问题 随着运行时间的增长 内存就会越来越少了 最后可能使得操作系统崩溃!!!

  • 相关阅读:
    随机数发生器设计(四)
    Go语言中获取协程ID
    TeaPearce/Conditional_Diffusion_MNIST 源码阅读
    NoSQL数据库使用场景以及架构介绍
    字节跳动五面都过了,结果被刷了,问了hr原因竟说是...
    大数据ClickHouse(十一):MergeTree系列表引擎之AggregatingMergeTree
    23.09.26用户切库流程记录
    AppLovin 175 亿美元收购游戏引擎Unity?
    java SSM农产品订购网站系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计
    Redis——》redis.conf
  • 原文地址:https://blog.csdn.net/xbhinsterest11/article/details/125862190