首页 > 系统相关 >C++内存管理

C++内存管理

时间:2023-05-19 20:33:00浏览次数:52  
标签:malloc 管理 C++ free 内存 operator new delete

C/C++内存分配

在一个程序的进程地址空间中,其内存分配如下:

C++内存管理_operator new

用来存储非静态局部变量、函数参数/返回值等,栈是向下增长的;用于程序的动态内存分配,堆是向上增长的;数据段用来存储全局数据和静态数据;代码段用来存储可执行指令,只读常量,字符串常量就存储在代码段中。数据段和代码段在语言层面又分别称为静态区和常量区

C++内存管理_operator delete_02

C的动态内存管理

C语言中用来管理动态内存的函数有:malloc、calloc、realloc和free。malloc 用来申请一块指定大小的堆内存空间;calloc 用来申请一块指定大小的堆内存空间,并将其初始化为 0;realloc 用来扩容动态空间;free 用来释放动态内存空间。关于更详细的C语言内存管理,请参考《C语言动态内存管理》。


C++的内存管理

C 语言中的内存管理方式在 C++ 中可以继续使用,但是在有些地方会造成不可避免的麻烦,甚至无法满足需求。因此 C++ 提出了一套自己的内存管理方式。

new和delete关键字

new用来申请一块动态内存空间,使用时不需要指定内存空间大小,只需要用类型进行标识。delete用来释放动态内存空间。当申请和释放连续的内存空间时,需要使用new[]delete[]。new的使用支持初始化。

class A
{
private:
	int _i;

public:
	A(int i = 10)
		:_i(i)
	{
		cout << "A(int i)" << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
};

int main()
{
	//内置类型
	int* pI1 = new int;
	delete pI1;

	double* pD1 = new double(3.14);
	delete pD1;
	//自定义类型
	int* pArr1 = new int[10];
	delete[] pArr1;

	int* pArr2 = new int[10] { 1, 2, 3 };
	delete[] pArr2;
	//类
	A* pA1 = new A(20);
	delete pA1;
  A* pA2 = (A*)malloc(sizeof(A)); //为了方便,此处不再进行检查
  free(pA2)
	cout << "test" << endl;

	return 0;
}

运行上面的程序,注意到 new 和 delete 在为类对象开辟内存空间时,会调用构造函数和析构函数,而malloc和free不会。这一点是 new/delete 和 malloc/free 之间最大的区别

new和delete的实现原理

operator new和operator delete

在 C 语言中,若一个函数处理失败,往往是通过返回值进行反馈的,例如malloc 开辟空间失败会返回 NULL;而在 C++ 等面向对象的语言中,任务处理失败一般不用返回值,而是抛出异常

C++ 是在C语言的基础上发展而来的,对于开辟和释放内存空间的工作,C++完全可以使用malloc和free完成,但是为了使malloc和free的使用更符合C++的特性和需求,需要对二者进行封装operator new operator delete就此而生。

operator new 和 operator delete 是系统提供的全局函数,operator new 通过 malloc 进行空间申请,如果申请成功则直接返回,如果最终无法申请成功,则抛出异常;operator delete 最终通过free释放空间。二者的参考源码如下:

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
			static const std::bad_alloc nomem;
			_RAISE(nomem);
		}
	return (p);
}

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_dbg 的定义为:

#define free(p) _free_dbg(p, _NORMAL_BLOCK)

在V.S.2022编译器下,operator new 和 operator delete 的源码如下:

_CRT_SECURITYCRITICAL_ATTRIBUTE
void* __CRTDECL operator new(size_t const size)
{
    for (;;)
    {
        if (void* const block = malloc(size))  {
            return block;
        }
        if (_callnewh(size) == 0)
        {
            if (size == SIZE_MAX) {
                __scrt_throw_std_bad_array_new_length();
            }
            else {
                __scrt_throw_std_bad_alloc();
            }
        }
        // The new handler was successful; try to allocate again...
    }  
}

_CRT_SECURITYCRITICAL_ATTRIBUTE
void __CRTDECL operator delete(void* const block) noexcept
{
    #ifdef _DEBUG
    _free_dbg(block, _UNKNOWN_BLOCK);
    #else
    free(block);
    #endif
}

new和delete

通过上述分析,并观察程序运行的汇编代码,此时已经可以窥探 new 和 delete 的原理:new 和 delete 是用户进行动态内存开辟和释放的操作符,new通过调用 operator new 函数进行空间申请,申请成功后,调用类对象的构造函数进行对象初始化(若是对象),申请失败则抛出异常;delete 首先调用类对象的析构函数,释放对象所开辟的内存空间,然后通过 operator delete 进行对象本身占用的空间的释放。

C++内存管理_new/delete_03

C++内存管理_new/delete_04

placement-new

定位new placement-new,是 new 操作符的一种用法,用来在一块已申请空间的基础上,调用类对象的构造函数初始化一个或多个对象。

class A
{
private:
	int _i;

public:
	A(int i = 10)
		:_i(i)
	{
		cout << "A(int i)" << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
};

int main()
{
	A* pA = (A*)operator new(sizeof(A) * 10); //申请空间
	new(pA)A; //显示调用构造函数初始化一个对象
	new(pA + 1)A[2]{ A(1), A(2) }; //显示调用构造函数初始化多个对象

	pA->~A(); //显式析构
	(pA + 1)->~A();
	(pA + 2)->~A();

	operator delete(pA); //释放空间
	return 0;
}

需要注意的是,使用 placement-new 初始化出一个对象后,编译器不自动调用析构函数,用户必须自行显式调用

placement-new在池化技术中应用广泛。在 STL 的空间配置器中,为了避免频繁直接向堆区申请内存,提高效率,往往使用placement-new从内存池中构造对象。

template <class T1, class T2>
inline void _construct(T1* p, const T2& value) {
    new(p) T1(value);           // placement new. invoke ctor of T1
}

malloc/free和new/delete

malloc/free 和 new/delete 都是在堆上申请一块内存空间,并且都需要用户手动释放。不同在于下面几点:

  • malloc/free是函数,new/delete是操作符
  • malloc的返回类型是void*,使用时要进行类型转换,而 new 不需要;
  • 申请自定义类型空间时,malloc/free只会申请和释放空间,而new会在申请内存空间后调用构造函数完成初始化,delete会先调用析构函数清理对象资源,最后完成空间释放。
  • malloc申请空间失败时,返回的是 NULL,需要进行指针检查,而new不需要,但是new的使用需要捕获异常
  • malloc/free使用时不支持初始化,new/delete支持初始化;
  • malloc申请空间时需要明确要申请空间的大小,而new不需要,因为new后面跟的是类型;

其中前 4 点可以大致认为是malloc/free和new/delete之间特性和原理方面的区别,而最后 2 点是使用方面的区别。


内存泄漏

内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

长期运行的程序,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

内存泄漏是个复杂的问题,这里只做简单介绍,后续会对内存泄漏的避免和检测方法做一详细总结。

标签:malloc,管理,C++,free,内存,operator,new,delete
From: https://blog.51cto.com/158SHI/6314836

相关文章

  • c++打卡练习(35)
    求分母为40的所有真分数流程图:伪代码:源代码:#include<iostream>usingnamespacestd;intmain(){inti,j,num1,num2,t,n=0;for(i=1;i<40;i++){ num1=40; num2=i; while(num2!=0){ t=num1%num2; num1=num2; num2=t; } if(num1==1){ n++; ......
  • C++
    定义抽象基类Shape,由它派生出五个派生类:Circle(圆形)、Square(正方形)、Rectangle(长方形)、Trapezoid(梯形)和Triangle(三角形),用虚函数分别计算各种图形的面积,并求出它们的和。要求用基类指针数组。使它的每一个元素指向一个派生类的对象。#include<iostream>usingnamespacestd;cl......
  • C++ 如何快速实现一个容器的迭代器
    C++如何快速实现一个容器的迭代器引言C++的标准库中的容器都会提供迭代器,如果一个容器满足forward_range,那么这个容器一般会提供以下成员类型和函数:iteratorconst_iteratorbeginendbegincend如果该容器还满足bidirectional_range,那么该容器还会额外提供以下成员类型和......
  • 520用项目管理思维来过,相当炸裂!
    明天就是一年一度的520啦,阿道单身多年的同事刚京在四月成功使用SWOT分析模型相亲成功,牵手女嘉宾。二人眼看着就要迎来在一起后的第一个节日520,刚京却因为没有头绪而陷入了不知所措的焦虑。 团队成员齐上阵,用项目管理思维,教刚京如何过好这个520!​项目启动今年的520是......
  • 详解c++STL—容器set/multiset
    1、set基本概念1.1、功能所有元素都会在插入时自动被排序1.2、本质:set/multiset属于关联式容器,底层结构是用二叉树实现。1.3、set和multiset区别set不允许容器中有重复的元素multiset允许容器中有重复的元素2、set构造和赋值2.1、功能描述创建set容器以及赋值2.1、构造set<T>st;/......
  • c++局部静态变量是线程安全的
    mark一下。c++11之前,局部静态变量初始化并不是线程安全的。c++11之后,当局部静态在初始化的过程中,有新的获取,会阻塞等待初始化成功。classInstance{public://... staticGetInstace() { staticInstanceinstance; returninstance; }};new,理论上应该也是可以的,......
  • 《C++ string类》
    1.string类常见的构造函数1)string(constchar*s):将string对象初始化为s指向的字符串stringstr("Hello!"); 2)string(size_typen,charc):创建一个包含n个元素的string对象,其中每个元素都被初始化为字符cstringstr(10,'a'); 3)string(conststring&str)......
  • C++中使用强类型的Enum Class
    在C++中,有Enumclass这种说法,在EffectivemodernC++这本书中,也提到Preferscopedenumstounscopedenum,就是说要用有范围的enumclass代替没有范围的enum.为什么会有这个问题呢?我们来看一个C++里面使用传统enum的例子:enumShape{circle,retangle};autocircle=10;......
  • Linux基础22 进程的优先级nice, 后台进程管理, 系统平均负载, 系统启动流程
    进程的优先级:nice值越高:表示优先级越低,例如19,该进程容易将CPU使用量让给其他进程。nice值越低:表示优先级越高,例如-20,该进程更不倾向于让出CPU。#以设定的优先级启动nice-n-10tail-f/var/log/messages#重新设置一个进程的优先级(调整sshd的优先级)[root@oldboyedu~]#......
  • 开心档之C++ Web 编程
    C++Web编程什么是CGI?公共网关接口(CGI),是一套标准,定义了信息是如何在Web服务器和客户端脚本之间进行交换的。CGI规范目前是由NCSA维护的,NCSA定义CGI如下:公共网关接口(CGI),是一种用于外部网关程序与信息服务器(如HTTP服务器)对接的接口标准。目前的版本是CGI/1.1,CGI/......