学习C/C++系列(10)C/C++内存管理

学习C/C++系列(10)C/C++内存管理

c/c++的内存分布

c/c++的程序内存大致可以分为6个部分

  1. 内核空间,用户代码,无法读写。
  2. 栈,向下增长,即从高地址向低地址生长。
  3. 内存映射段,只要为文件映射,动态库,匿名映射。是一种高效的I/O映射方式用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。
  4. 堆,向上增长,即从低地址向高地址增长。
  5. 数据段,用于存储全局数据和静态数据。
  6. 代码段,用于存储可执行的代码和只读常量。

c语言的动态内存管理

c语言的动态内存管理主要为malloccallocreallo三个函数,还有一个释放空间的free函数。

对于这四个函数,可以参见另一篇博客https://blog.csdn.net/roseisbule/article/details/122915193。

c++的动态内存管理

  1. c++仍然支持c的内存管理,但是c++给出了更好的动态内存申请方法,new和delete。

    #include <iostream>
    using std::endl;
    using std::cout;
    using std::cin;
    
    
    int main()
    {
    	int* p1 = new int;      // 申请一个int内存
    	int* p2 = new int(10);  // 申请一个int内存 并初始化为10
    	int* p3 = new int[10];  // 申请十个int内存 即申请一个个数为10的int数组
    	delete p1;
    	delete p2;
    	delete p3;
    	return 0;
    }
    

    new和delete的用法如上,可以看到new和delete的用法更为简洁,并且不需要自己再手动写判断所需空间是否new得出来。因为new不出来,会直接报错,这可以在后面的new代码分析看出。new和delete的好处不止这些。

  2. new和delete是可以操作自定义类型的。

    #include <iostream>
    using std::endl;
    using std::cout;
    using std::cin;
    
    
    class data
    {
    public:
    	data(int a = 1, int b = 2, char c = 'x')
    		:_a(a),
    		_b(b),
    		_c(c)
    	{
    		cout << "data" << endl;
    	};
    	~data()
    	{
    		cout << "~data" << endl;
    	}
    private:
    	int _a;
    	int _b;
    	char _c;
    };
    
    int main()
    {
    	data* x = new data;
    	delete x;
        cout << "main end" << endl;
    	return 0;
    }
    

    运行结果

运行可以看到构造函数和析构函数在main结束之前被调用了。进一步调试可以看到,当new调用的时候,构造函数被调用,当delete调用时,析构函数被调用。

说明,当new操作自定义类型时,会调用该类型的默认构造函数,当delete操作自定义类型时,会调用该类型的默认析构函数。但是malloc和free不会。

operator new 和 operator delete

  1. new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。

    /*
    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)
    

    通过上面两个全局函数可以看出,new的底层还是通过malloc来申请空间,如果malloc申请空间成功,则直接返回申请到的空间的地址。如果申请失败,则执行用户提供的空间不足时的应对方法。如果有该方法则执行该方法,如果没有该方法,则抛出异常。delete的底层也是调用的free。

  2. operator new 和 operator delete的类专属重载。

    一般情况下, 不需要重载 new 和 delete函数, 但是有时, 我们在申请释放空间时有特殊的需求. 如, 我们想要在使用 new 和 delete时, 打印一些日志信息, 以判断是否有内存泄漏.

new 和 delete 的实现原理

  1. 内置类型

    如果申请的是内置类型. 那么 c++ 的 new 和 delete , 和 c 的 malloc 和 free 基本类似, 不同的地方在于, new/delete 申请和释放的是单个元素的空间, new[]/delete[] 申请和释放的是连续空间, 并且 new 在申请空间失败的时候会直接报错, 而不是 malloc 的返回空指针.

  2. 自定义类型

    a. new的原理

    new 会调用类的 operator new 函数申请空间, 然后调用构造函数, 完成对象的构造.

    b. delete的原理

    delete 会调用类的析构函数销毁对象, 然后调用 operator delete 函数释放对象占据的空间.

    c. new[] 的原理

    new[] 会调用 operator new[]函数, operator new[]实际上会调用 operator new 函数完成多个对象的空间申请. 在每一个对象的空间上, 都会调用构造函数对对象初始化.

    d. delete[] 的原理

    delete[] 会多次调用 析构函数 销毁对象, 然后调用 operator delete[] 函数. 而 operator delete[] 函数会多次调用 operator delete 函数释放空间.

定位new表达式

定位new表达式是在已经分配的内存中去初始化对象. 使用格式为 new (place_address) type 或者 new (place_address) type (initializer-list), 其中的 place_address 必须为一个指针, initializer-list 为初始化列表.定位new表达式多用于内存池. 因为内存池所分配的内存没有初始化, 所以如果是自定义类型的对象, 需要使用定位new表达式进行显式的调用构造函数进行初始化.

malloc/free 和 new/delete 的区别

malloc/free 和 new/delete 都是从堆上申请空间, 并且需要用户手动释放, 但是有下面不同之处.

  1. malloc/free 是函数, new/delete 是操作符.
  2. malloc 申请的空间不会初始化, 而 new 会, 它会去调用构造函数.
  3. malloc 申请空间必须提前知道大小, 而 new 只需告诉它要申请那种类型, 不需要手动输入大小.
  4. malloc 的返回值类型为 void* , 而 new 返回的就是申请类型的指针.
  5. malloc 申请内存失败时, 返回 NULL, 而 new 申请失败时, 直接报错.
  6. 申请自定义类型的空间时, new 会自动掉构造函数, delete 会自动掉析构函数, 而 malloc/free 不会.

内存泄漏

当我们申请了一段内存, 但是我们就是不使用, 程序也不退出, 内存也不释放. 那么该程序就一直占着该内存. 如果一个程序永不退出, 而他 每秒 都会申请空间, 而不释放. 那可想而知, 我们能用的内存就会越来越少, 因为被该程序给占了. 这就是内存泄漏, 长期运行的程序如果出现内存泄漏, 会导致响应越来越慢, 最终卡死.

一般有两种内存泄漏, 堆内存泄漏, 系统资源泄漏. 其中堆内存泄漏, 就是想上面那样, 申请内存而不释放. 而系统资源泄漏 是因为程序 调用系统的接口 得到的资源, 如套接字, 文件描述符, 管道, 共享内存, 等因为没有调用对应的函数释放.

对于检测内存泄漏, windows 提供了 _CutDumpMemoryLeadk() .

对于内存泄漏, 我们无法保证不i会写内存泄漏的程序, 我们能做的就是保持良好的编程习惯, 降低内存泄漏的可能性.
内存而不释放. 而系统资源泄漏 是因为程序 调用系统的接口 得到的资源, 如套接字, 文件描述符, 管道, 共享内存, 等因为没有调用对应的函数释放.

对于检测内存泄漏, windows 提供了 _CutDumpMemoryLeadk() .

对于内存泄漏, 我们无法保证不i会写内存泄漏的程序, 我们能做的就是保持良好的编程习惯, 降低内存泄漏的可能性.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

roseisbule

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值