C/C++:内存管理

一.C/C++中程序内存区域划分

在C/C++中,程序内存区域可以分成四个部分:

1.栈:用来保存非静态的局部变量/函数参数/返回值等等,栈试想下增长的    

2.堆:用于程序运行时的动态内存分配,堆是可以向上增长的

3.数据段:存储全局数据和静态数据

4.代码段:保存可执行的代码和只读的常量

二.C语言动态内存的管理方式

C语言我们使用的开辟内存空间的函数有三个:

1.malloc: void *malloc(size_t size);                              用来申请size个字节的空间

2.calloc: void *calloc(size_t nmemb, size_t size);        用来申请size*nmemb个字节的空间,并对内存初始化

3.realloc: void *realloc(void *ptr, size_t size);              如果size小于或者等于ptr指向的空间,则保持不变,否则重新分配内存空 间,把之                                                                                        前的数据搬移到引得空间中

所以:relloc(NULL,size)=malloc(NULL,size)

用来内存释放的只有一个函数:

free:  void free(void *ptr);

void test()
{
	int* p1 = (int *)malloc(sizeof(int)* 10);
	int* p2 = (int *)calloc(4,sizeof(int));
	int* p3 = (int *)realloc(p2,sizeof(int)* 6);
	free(p1);
	free(p3);
	//free(p2);在这不应该重复释放,因为p3就是在p2的地址上多申请sizeof(int)* 6个字节,不能重复释放
}

我们在用malloc申请的空间一定要记住:要用free释放,不然就会内存泄漏。

当我们没有释放内存我们可以通过系统给的函数自己检测:

#include<crtdbg.h>

CrtDumpMemoryLeaks();

假如没有内存释放,就会显示如下:

三.C++的动态内存管理方式

C语言的动态内存的管理方式在C++中同样适用,只不过C++有自己的动态内存管理方式:C++中通过new和delete来进行动态管理。

new/delete动态管理对象

newp[]/delete[]动态管理对象数组

void test()
{
	int* p1 = new int;       分配一个int的单个数据空间
	int* p2 = new int(3);    分配一个int的单个数据空间,(3)是初始化的值
	int*p3 = new int[3];     分配三个int的数据空间
	delete p1;
	delete p2;
	delete[] p3;
}

在C++中:new和delete,new[]和delete[]一定要匹配使用!一定要匹配使用!!一定要匹配使用!!!

malloc/free和new/delete的比较

看代码:

void test()
{
	int* p1 = (int*)malloc(sizeof(int));
	int* p2 = (int*)malloc(sizeof(int));
	int* p3 = new int;
	int* p4 = new int;
	int* p5 = new int[10];
	int* p6 = new int[10];

	delete p1;
	delete[] p2;

	free(p3);
	delete[]p4;

	free (p5);
	delete p6;
}

我们分别用malloc和new申请空间,但是在释放的时候,却用的其他两种释放的形式。那么这样的代码会出错吗?有没有内存泄漏?

通过调试,我们发现代码不仅不会出错,而且并没有出现内存泄漏。

那么我们在加一个类再来调试:

class Test
{
public:
	Test()
             :_data(0)
	{
		cout << this << endl;
	}
	~Test()
	{
		cout << this << endl;
	}
private:
	int _data;
};
void test()
{
	Test* p1 = (Test*)malloc(sizeof(Test));
	Test* p2 = (Test*)malloc(sizeof(Test));
	Test* p3 = new Test;
	Test* p4 = new Test;
	Test* p5 = new Test[10];
	Test* p6 = new Test[10];

	delete p1;
	delete[] p2;

	free(p3);
	delete[]p4;

	free (p5);
	delete p6;
}
int main()
{
	test();
	_CrtDumpMemoryLeaks();
	system("pause");
	return 0;
}

当我们在运行的时候发现每当遇到delete[]的时候代码就会触发断点二崩溃。

我们按照正常的释放方式运行一遍:

class Test
{
public:
	Test()
	{
		cout << this << endl;
	}
	~Test()
	{
		cout << this << endl;
	}
private:
	int _data;
};
void test()
{
	Test* p1 = (Test*)malloc(sizeof(Test));
	Test* p2 = (Test*)malloc(sizeof(Test));
	Test* p3 = new Test;
	Test* p4 = new Test;
	Test* p5 = new Test[3];
	Test* p6 = new Test[3];
        
        printf("\n");
        
	free(p1);
	free(p2);
	//delete[] p2;

	delete(p3);
	delete p4;
	//delete[]p4;

	delete[]p5;
	delete[]p6;
	//free (p5);
	//delete p6;
}
int main()
{
	test();
	_CrtDumpMemoryLeaks();
	system("pause");
	return 0;
}

运行结果:

所以在C++之所以加入了new和delete是因为它们分别需要调用构造函数和析构函数。

四.new和delete的实现原理

1.内存类型

new和delete申请的是单个元素的空间,而new[]和delete[]申请的是一段连续的空间。new在申请空间失败时会抛出异常,而malloc会返回空指针。

2.自定义类型

(1)new的原理

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

(2)delete的原理

在申请的空间上调用析构函数,清理对象中的资源,然后调用operator delete函数释放对象的空间

(3)new[]的原理

调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请,然后在申请的空间上执行N次构造函数,完成对N个对象的构造

(4)delete[]的原理

在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理 ,再调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空

五.定位new表达式

定位new表达式就是给已经申请好的空间调用构造函数来初始化一个对象。

看如下代码:我们用malloc申请的空间,用定位new表达式来对对象进行初始化。

class Test
{
public:
	Test()
		:_data(0)
	{
		cout << this << endl;
	}
	~Test()
	{
		cout << this << endl;
	}
private:
	int _data;
};
void test()
{
	//pt现在指向的只不过是与Test对象相同大小的一段空间,
	//还不能算是一个对象,因为构造函数没有执行   
	Test* pt = (Test*)malloc(sizeof(Test));      
	new(pt) Test;  //定位new表达式的格式
}
int main()
{
	test();
	_CrtDumpMemoryLeaks();
	system("pause");
	return 0;
}

六.malloc/free和new/delete的区别

我们都知道这malloc/free和new/delete都是在堆上申请空间,都需要通过用户手动进行释放,但是它们的区别有很多。

1.区别:

(1)malloc/free时函数,而new和delete时操作符

(2)malloc申请的空间不能初始化,而new申请时会调用构造函数对对象进行初始化

(3)malloc申请空间时需要传递申请空间的大小,new只需要跟上类型,由编译器自动识别。

(4)malloc申请的空间需要强制类型转化

(5)malloc申请空间失败时,返回的是空指针,因此使用时必须判空,new不需要,但是new需要捕获异常 

(6)malloc申请的空间一定在堆上,而new不一定,因为operator new可以由用户自己实现。

(7)new/delete的效率稍低,因为new/delete是由malloc/free封装而成的

七.面试题

1.创建一个类,该类只能在堆上申请空间

思路:在堆上创建一个类,我们可以把构造函数和拷贝构造函数私有化,然后创建一个静态的函数来完成对对象的创建

class onlyheap
{
public:
	static onlyheap* CreateObject()
	{ 
		return new onlyheap;
	}
private:
	onlyheap(){};//构造函数
	onlyheap(const onlyheap&);
};

2.创建一个类,该类只能在栈上申请空间

只有使用了new操作符,对象才会创建在堆上,因此只要禁用new运算符就可以实现类对象只能创建在栈上,将operator new设为私有

class StackOnly    
{ 
public:        
	StackOnly()  {} 
private:        
	void* operator new(size_t size);    
	void operator delete(void* p);
};

猜你喜欢

转载自blog.csdn.net/oldwang1999/article/details/83932840
今日推荐