C++入门——动态内存管理

- C/C++内存分布

从上往下:

  1. 内核空间
  2. 栈。存储局部变量,往低地址增长。空间比较小,但是申请释放内存速度极快
  3. 内存映射段。用于装载一个共享的动态内存库,用户可使用系统接口创建共享共享内存,做进程间通信。
  4. 堆。动态开辟的空间,往高地址增长。空间非常大,申请释放速度较慢,且需要手动释放
  5. 数据段。存储全局变量和静态变量
  6. 代码段。存储函数二进制指令,常量
- C语言中动态内存管理方式
  • 申请
    使用 malloc 函数申请空间,返回一个void指针,如果申请失败则返回NULL
  • 释放
    使用 free 释放空间,参数可以传NULL
  • 拓展
    calloc
    函数原型:void* calloc(unsigned int num, unsigned int size);
    函数功能:return malloc(num * size);
    realloc
    函数原型:extern void *realloc(void *mem_address, unsigned int newsize);
    函数功能:给mem_address申请一块newsize大小的空间,将原空间的内容按字节拷贝到新空间上,并返回新的地址。申请失败返回NULL
    特殊情况:若newsize为0,则返回NULL并free原空间
- C++中动态内存管理

申请和释放单个元素的空间:new/delete
new 类型(初始化列表)
delete 地址
申请和释放连续的空间:new[]/delete[]
new 类型[对象的个数]
delete[] 地址

  • 与malloc/free的区别
  1. malloc和free是函数,new和delete是操作符
  2. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可
  3. malloc的返回值为void*, 在使用时必须强转,new不需要
  4. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
  5. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理

使用方法举例:

class date {
public:
	date(int year = 1970, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day) {	}
private:
	int _year;
	int _month;
	int _day;
};

int main() {
	date* d = new date(1999, 6, 1);
	date* pd = new date[5];
	delete d;
	delete[] pd;
	return 0;
}

需要注意的是,代码中的new data[5]在申请空间之后会调用5次构造函数,所以若是data没有默认构造的话编译器将会报错。

- new和delete的实现原理
  • 内置类型
    这种情况下new和malloc,delete和free基本类似

  • 自定义类型
    new的原理

    1. 调用operator new函数申请空间
    2. 在申请的空间上执行构造函数,完成对象的构造

    delete的原理

    1. 在空间上执行析构函数,完成对象中资源的清理工作
    2. 调用operator delete函数释放对象的空间

    new T[num]的原理

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

    delete[]的原理

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

总结:new和delete本质上是对malloc和free的封装,申请释放空间的功能依旧是由malloc和free完成的,只不过增加了调用构造函数、析构函数、抛异常等功能

- 定位new表达式(placement-new)

在已分配的原始内存空间中调用构造函数初始化一个对象
使用格式:
new(地址) 类型名new(地址) 类型名(初始化参数列表)

- 一些应用场景
  • 设计一个只能在堆上创建对象的类
    只能在堆上创建对象,意味着不能在栈上创建对象(废话),也就是说在栈上创建对象的方法要被禁用。
    而类的6个默认成员函数中,能在栈上创建对象的有构造函数和拷贝构造函数,只要将它们禁用就行了。
    需要注意的是,使用new创建对象也是需要调用构造函数,因此这里只需要禁用拷贝构造,而构造函数则只需要封装一下。
    下面举个例子演示一下
    class HeapOnly {
    public:
    	//声明为静态类型才可以直接通过类名调用
    	static HeapOnly* CreatObject() {
    		return new HeapOnly(1);
    	}
    private:
    	//构造函数不能被禁用
    	HeapOnly(int num) :x(num){};
    	//拷贝构造随便禁
    	HeapOnly(const HeapOnly&) = delete;
    	int x;
    };
    
  • 设计一个只能在栈上创建对象的类
    同上面的情况类似,需要将operator new系列的函数禁用
    class StackOnly {
    public:
    	StackOnly() {};
    private:
    	//禁止使用new创建单个对象
    	void* operator new(size_t) = delete;
    	//禁止使用new连续创建多个对象
    	void* operator new[](size_t) = delete;
    };
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值