【c++】c/c++内存管理

目录

内存管理

c/c++程序中的内存分布

C语言内存管理方式

malloc / calloc / realloc / free

C++内存管理方式

1. new/ delete 操作内置类型

2. new/ delete 操作自定义类型

operator new和 operator delete

对于内置类型

对于自定义类型

定位new表达式

常见的几个问题

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

2. 设计一个只能在堆上创建对象的类

3.设计一个只能在栈上创建的类

内存泄漏

1. 概念

2.分类

3.避免


内存管理

c/c++程序中的内存分布

    我们先来看一下c/c++程序中的内存到底是怎样分布的, 如下图:

1. 栈 : 又叫堆栈, 存放非静态的局部变量/函数的参数/返回值等等, 栈在内存中是向下增长的, 通常栈是一个很小的空间, 一般  只有几M的大小

int main()
{
    int i = 0;
    char c = 'a';
    int *p = &a;
    int arr[4] = {1,2,3,4};
}

  代码中i, c, *p, arr[4]这些都是存在栈上的

2. 内存映射段 : 比较特殊, 如果我们想读一些文件但是文件过大, 就会映射一些, linux里面的共享内存也是通过这种内存映射来实现的, 了解即可

3. 堆 : 用于程序运行时的动态内存分配, 堆是向上增长的, 在C语言中,堆空间只能用指针访问, c++中引用也可以访问

int *ptr = (int*)malloc(sizeof(int) * 4);

    malloc申请出来的空间就是在堆上申请的,realloc, calloc出来的空间也是在堆上, 总而言之就是, 动态申请的内存都在堆上

4. 数据段 : 存放全局数据和静态数据

int a = 10;
int main()
{
    static int b = 20;
    return 0;
}

  a, b, 都是在数据段的, 因为他们并不是局部变量

5. 代码段 :  存放可执行的代码和只读常量

int main()
{
    char *arr = "nihao";
}

  "nihao"这种只读常量就是存放在代码段中的

 

C语言内存管理方式

malloc / calloc / realloc / free

1. void *malloc(size_t size);  只开辟空间, 不初始化

2. void *calloc(size_t nmemb, size_t size);  开辟空间, 按字节进行初始化

3.  void *realloc(void *ptr, size_t size);  调整空间

    newsize < 原指针指向的空间 : 只修改结束位置底层标记, 释放多余的空间

    newsize > 原指针指向的空间 : 

        1. 如果原指针后面还有足够的空间, 只修改结束位置底层标记

        2. 如果原指针后面没有足够的空间, 开辟一个更大的空间, 拷贝原有空间的内容, 释放原有空间

4. void free(void *ptr);  释放动态申请的空间, 需要手动释放

我们在代码中体会:

#include <iostream>
using namespace std

int main()
{
	//malloc : 只开辟空间, 不会初始化
	int *p = (int*)malloc(sizeof(int) * 4);
	cout << "malloc: " << *p << endl;
	cout << p << endl;

	//calloc : 开辟空间, 按字节进行初始化
	int *p1 = (int*)calloc(8, sizeof(int));
	cout << "calloc: " << *p1 << endl;
	cout << p1 << endl;

	//realloc(void*, newsize) : 调整空间
	//1. size < 原有空间
	int *p2 = (int*)realloc(p1, sizeof(int) * 4);
	cout << "realloc1: " << *p2 << endl;
	cout << p2 << endl;
	//2. size > 原有空间
	int *p3 = (int*)realloc(p1, sizeof(int) * 1024);
	cout << "realloc2: " << *p3 << endl;
	cout << p3 << endl;

	free(p);
	//free(p1);  不需要手动释放, realloc2中重新开辟空间
	//			的时候已经把p1自动释放了, 会造成二次释放
	free(p3);
	system("pause");
	return 0;	
}

运行结果:

  可以看出malloc并没有初始化, realloc的newsize > oldsize的时候重新开辟了空间, 指针指向了新的地址

 

C++内存管理方式

    C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力而且使用起来比较麻烦,因此C++提出了的内存管理方式:通过new和delete操作符进行动态内存管理

new和delete使用我们直接在代码中看

1. new/ delete 操作内置类型

#include <iostream>
using namespace std;

int main()
{
    //开辟
	//1.未初始化
	int *p = new int;
	cout << *p << endl;
	//2.初始化
	int *p1 = new int(5);
	cout << *p1 << endl;
	//3.开辟一个有10个int类型数据的数组
	int *p2 = new int[10];
	cout << *p2 << endl;
        //销毁
        //单个数据用delete, 数组用delete[]
	delete p;
	delete p1;
	delete[] p2;

	system("pause");
	return 0;	
}

  注意: 申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[]

2. new/ delete 操作自定义类型

我们写一个简单的类:

class M
{
public:
	M(int a = 10)
		:_a(a)
	{
		cout << "M(int)" << endl;
	}
	~M()
	{
		cout << "~M()" << endl;
	}
	void display()
	{
		cout << _a << endl;
	}
private:
	int _a;
};

int main()
{
	//1.不进行初始化
	M* m = new M;
	m->display();
	delete m;
	//2.初始化
	M* m1 = new M(20);
	m1->display();
	delete m1;
	//3.开辟一个M[5]数组
	M* m2 = new M[5];
	delete[] m2;
	//4.malloc开辟
	M* m3 = (M*)malloc(sizeof(M) * 1);
	free(m3);

	system("pause");
	return 0;
}

运行:

我们可以看到:

    m调用了一次构造, 编译器自动将_a赋值成了10, delete后调用构造函数

    m1调用了一个构造, 但是我们自己初始化了, 所以打印出来的值就是20, 同样释放调用析构

    m2数组创建了5个M类, 调用了5次构造, 5次析构

注意:

    1. 在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会

    2. new[] 申请的空间 必须用delete[] 释放, 否则会造成内存泄漏

 

operator new和 operator delete

    new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间, 注意operator在这里并不是重载

对于内置类型

  1. operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败, 尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常
  2. operator delete : 底层封装了free, 最终是通过free释放空间的

内置类型调用过程:  

   new -> operator new -> malloc

   delete -> operator delete -> free

对于自定义类型

   我们也可以自定义空间申请的方式, 来看一段代码:

   重载operator new 和 operator delete, 从内存池中申请和归还空间

#include <iostream>
using namespace std;
class M
{
public:
	M(int a = 10)
		:_a(a)
	{
		cout << "M(int)" << endl;
	}
	~M()
	{
		cout << "~M()" << endl;
	}
	void* operator new(size_t n)
	{
		M* p = (M*)allocator<M>().allocate(1);
		cout << "operator new" << endl;
		return (void*)p;
	}
	void operator delete(void* ptr)
	{
		allocator<M>().deallocate((M*)ptr,1);
		cout << "operator delete" << endl;
	}
private:
	int _a;
};

int main()
{
	M* m = new M;
	delete m;
	system("pause");
	return 0;
}

运行结果:

  可以看到对于自定义类型, 我们new的时候先调用operator new 再调用构造函数, operator new 如果资源申请失败会抛异常, 但是delete的时候是先调用析构函数, 再执行operator delete 进行内存的回收.

  如果我们不显式的定义的话, new的时候就会调用默认生成的

自定义类型调用过程 : 

     new -> operator new -> malloc  -> 构造函数

     delete -> operator delete -> 析构函数 -> free

 

定位new表达式

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象

使用场景:

  定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,

需要使用new的定义表达式进行显示调构造函数进行初始化

代码感受一下:

#include <iostream>
using namespace std;
class test
{
public:
	test()
		:_a(10)
	{
		cout << "test()" << endl;
	}
	~test()
	{
		cout << "~test()" << endl;
	}
	void display()
	{
		cout << _a << endl;
	}
private:
	int _a;
};
int main()
{
	test* tp = (test*)malloc(sizeof(test) * 1);
        //tp现在指向的只不过是与Test对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
	new(tp) test;
	tp->display();
	delete tp;
	system("pause");
	return 0;	
}

运行结果:

  可以看到构造函数执行了,并且delete的时候也调用了delete ,如果不加 new(tp) test, 输出的就是一个随机值,并且不会调用构造函数

 

常见的几个问题

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

共同点:

    都是从堆上申请空间,并且需要用户手动释放

不同点:

    1. malloc和free是函数,new和delete是操作符

    2. malloc申请的空间不会初始化,new可以初始化

    4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型

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

    6. malloc 和 free 不会调用构造和析构函数, new 和 delete会调用

    7. new/delete比malloc和free的效率稍微低点,因为new/delete的底层封装了malloc/free

2. 设计一个只能在堆上创建对象的类

 方法 : 

    将类的构造函数和拷贝构造函数声明成私有,防止别人调用拷贝构造在栈上生成对象

    提供一个静态的成员函数, 在该成员函数中创建一个堆上的对象并且返回

代码:

class Heap
{
public:
	static Heap* create()
	{
		return new Heap;
	}
private:
	Heap(){}
	//c++98 只声明不实现
	Heap(const Heap&);
	//c++11, 定义成为删除函数
	Heap(const Heap&) = delete;
};

 

3.设计一个只能在栈上创建的类

方法:

1. 屏蔽掉new关键字, 就不能在堆上创建了, 不能在类外访文私有成员

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

2. 构造函数私有化, new不能调用构造函数, 提供一个公有的静态方法

class Stack
{
public:
	//提供公有静态方法, 内部在栈上创建对象
	static Stack getStack()
	{
		return Stack();
	}
private:
	//构造函数私有
	Stack(){}
};
int main()
{
	Stack s = Stack::getStack();
	system("pause");
	return 0;	
}

 

 

内存泄漏

1. 概念

    内存泄漏指因为个人疏忽或错误造成程序未能释放已经不适用的或者申请内存。并不是物理上的消失, 只是对这段内存不能再访文了.

    举个例子:

class A
{
public:
	A()
		:_a(10)
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};
int main()
{
	A* pa = new A[5];
	//我们new[] 申请的内存应该用delete[] 来释放
	//但是这个是时候我们只释放一个, 看看效果
	delete pa;
	system("pause");
	return 0;	
}

运行结果:

我们可以看到程序崩溃了, 我们new[] 申请的内存应该使用delete[] 释放, 但是我们只释放一个, 这个时候就会造成内存泄漏

2.分类

堆内存泄漏 

    堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中动态分配的一块内存,用完后必须通过

调用相应的 free或者delete 删掉。假设这部分内存没有被释放,那么以后这部分空间将无法再被使用

系统资源泄漏

    指程序使用系统分配的资源,比方套接字socket、文件描述符fd、管道等使用后没有关闭掉,导致系统资源的浪费,严重

可导致系统效能减少,系统执行不稳定

3.避免

    (1)养成良好的代码习惯, 申请内存记得释放

    (2)可以使用智能指针来管理资源, 后面我们会说到

    (3)使用内存检测工具 ---网上很多

内存管理的内容大概就这么多, 谢谢观看

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值