【C++深入浅出】C/C++内存管理(教你如何new到对象)

这篇具有很好参考价值的文章主要介绍了【C++深入浅出】C/C++内存管理(教你如何new到对象)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

【C++深入浅出】C/C++内存管理(教你如何new到对象),C++深入浅出,c++,开发语言,内存管理,堆,new和delete


一. 前言

        前面我们学习了有关C++类和对象的知识,学会了如何构建一个完整的类,这些类都是存储在栈空间上的。在C语言中,我们不仅可以在栈上定义变量,也可以对上的空间进行管理,在接下来的几期中,我们的目标就是学会C++中是如何进行内存管理的

        没有对象的兄弟们都看过来啦,接下来的内容就是教你如何new一个对象出来,学习完本章节内容,保你们人人都有对象,好好看好好学【C++深入浅出】C/C++内存管理(教你如何new到对象),C++深入浅出,c++,开发语言,内存管理,堆,new和delete

        话不多说,开整!!!

二. C/C++的内存分布

        在正式学习之前,我们先来看一下如下的示例代码:

#include<stdlib.h>
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
	static int staticVar = 1;
	int localVar = 1;
	int num1[10] = { 1, 2, 3, 4 };
	char char2[] = "abcd";
	const char* pChar3 = "abcd";
	int* ptr1 = (int*)malloc(sizeof(int) * 4);
	int* ptr2 = (int*)calloc(4, sizeof(int));
	int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
	free(ptr1);
	free(ptr3);
}

问:你能不能指出以上每个变量在内存中存储的位置?如果这个变量是个指针,那指针又是指向内存中的哪块区域?请画图分析

        我们知道,在C/C++中,内存被分为栈区、堆区、静态区、字符常量区不同的区域,每个区域存储的内容互有差别,具体可以回顾往期:

C语言地址空间http://t.csdn.cn/P5rkL        由此我们很容易可以看出,像globalVar这些全局变量是放在静态区(数据段)中,像num1这种非静态局部变量是存放在栈区的,像"abcd"这些字符串常量是存放在字符常量区(代码区)的,而像ptr1这些则是指向由malloc函数在堆区上所申请的空间。画图分析如下:【C++深入浅出】C/C++内存管理(教你如何new到对象),C++深入浅出,c++,开发语言,内存管理,堆,new和delete

三. C语言内存管理方式

        在C语言中,我们通常使用mallocrealloccalloc来进行动态内存管理。它们的函数原型和基本功能如下所示:

函数原型 功能说明
void* malloc(unsigned int num_bytes)

上动态申请一段num_bytes字节的空间,并返回这段空间的首地址,空间内的值不进行初始化,是随机值

void *calloc(size_t n, size_t size) 上动态申请n个size字节的空间,和malloc不同的是,空间内的值会被初始化为0
void realloc(void *ptr, size_t new_Size) 和上面两个函数不同,realloc主要是对已申请的内存空间进行扩容。ptr为指向原来空间的指针,new_size为扩容后内存空间的大小

        我们来看个小栗子:

void Test()
{
	int* p1 = (int*)malloc(sizeof(int)); //申请一个整形大小的空间,不进行初始化
	free(p1); //释放空间
	
	int* p2 = (int*)calloc(4, sizeof(int)); //申请4个整形大小的空间,初始化为0
	int* p3 = (int*)realloc(p2, sizeof(int) * 10); //对p2指向的空间进行扩容,扩容后的空间大小为10个整形的空间
	// 这里需要free(p2)吗?
	free(p3);
}

上面的p2还需要进行free()释放吗?答案是不用的。因为我们在之后对p2进行了扩容操作,而扩容分为原地扩容异地扩容

假如进行的是原地扩容,那么p2和p3指向的都是同一段空间,这时对p3进行释放就相当于对p2进行释放,如果此时我们又对p2进行了释放,则相当于对同一段空间进行多次释放,程序会崩溃

还有一种可能就是原空间所在分区后面剩余的空间不够了,此时需要进行异地扩容,那么系统就会在新的区域开辟一段空间,然后再将旧空间的数据拷贝下来,接着会自动释放旧空间,最后返回新空间的地址。

由此可见,对于p2,我们无需对其进行free()释放。

四. C++内存管理方式

        C++全面兼容C语言,故在C语言中进行动态内存管理的方式依然可以继续沿用。但在一些特殊场合使用C语言的方式无法达到目的;而且使用起来比较麻烦,需要进行各种函数调用、传参,因此C++提出了新的内存管理方式:通过newdelete操作符进行动态内存管理。

4.1 new/delete内置类型

        使用new和delete操作内置类型,基本上和malloc与free没什么区别,使用方法如下:

【C++深入浅出】C/C++内存管理(教你如何new到对象),C++深入浅出,c++,开发语言,内存管理,堆,new和delete

注意:new和deletenew[]和delete[]需要匹配进行使用,前者用于申请和释放单个元素空间,后者用于申请和释放连续的空间,不要混用,否则可能引发问题。

4.2 new/delete自定义类型

        new/delete操作符和malloc/free函数最大的区别体现在申请自定义类型中。使用new申请自定义类型的对象会自动调用构造函数,同理,使用delete释放自定义类型对象则会自动调用析构函数。下面我们来验证一下【C++深入浅出】C/C++内存管理(教你如何new到对象),C++深入浅出,c++,开发语言,内存管理,堆,new和delete

class A
{
public:
	A(int x)
		:num(x)
	{
		cout << "A(int x)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int num;
};
int main()
{
	cout << "测试new/delete----->" << endl;
	A* ptr1 = new A(3);
	delete ptr1;
	cout << "测试malloc/free---->" << endl;
	A* ptr2 = (A*)malloc(sizeof(A));
	free(ptr2);
	return 0;
}

【C++深入浅出】C/C++内存管理(教你如何new到对象),C++深入浅出,c++,开发语言,内存管理,堆,new和delete

五. operator new与operator delete函数

        上面我们介绍的new和delete是两个用户进行动态管理内存的操作符。而在系统中,C++还给我们提供了两个全局函数operator newoperator delete,注意,这两个函数不是运算符重载!!!new操作符在底层是调用operator new全局函数来申请空间,delete操作符在底层是通过
operator delete全局函数来释放空间。

        operator new函数在底层实际上也是通过malloc来申请空间的,当空间申请成功时直接返回,如果空间申请失败并且用户没有指定应对措施,就会抛出异常。而operator delete函数底层则是通过free来释放空间。
【C++深入浅出】C/C++内存管理(教你如何new到对象),C++深入浅出,c++,开发语言,内存管理,堆,new和delete

由此可见,new操作符在空间申请失败时默认会抛出异常,而malloc函数则是返回空指针 。

六. new和delete的实现原理

6.1 对于内置类型

        针对内置类型来说,new和delete实际和malloc和free基本类似。二者只有以下两处不同:

  1. 我们是通过new/delete申请/释放单个元素,通过new[]/delete[]申请/释放连续空间
  2. 使用new和new[]申请空间时,如果申请失败则会抛出异常,而malloc则是返回空指针

6.2 对于自定义类型

        而针对自定义类型来说,new和delete会自动调用构造和析构函数,它们的实现方式如下:

  • new 的原理

        1、底层调用operator new函数申请空间

        2、在申请的空间上执行构造函数,完成对象的构造【C++深入浅出】C/C++内存管理(教你如何new到对象),C++深入浅出,c++,开发语言,内存管理,堆,new和delete

  • delete 的原理

         1、先在已申请的空间上执行析构函数,完成对象中资源的清理工作

         2、然后调用operator delete函数释放该对象的空间

  • new T[] 的原理

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

  • delete[] 的原理

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


七. 定位new表达式

        在C++中是不允许通过对象显示调用构造函数的,只允许我们显式地调用析构函数,如下【C++深入浅出】C/C++内存管理(教你如何new到对象),C++深入浅出,c++,开发语言,内存管理,堆,new和delete

int main()
{
	A* ptr = (A*)operator new(sizeof(A));  //使用operator new函数开辟空间,注意operator new不会自动调用构造函数
	ptr->A(10); //通过对象显式调用构造函数,不允许
	ptr->~A(); //通过对象显式调用析构函数,允许
	return 0;
}

         可是有时候我们是需要对已分配的内存空间调用构造函数进行初始化的,例如使用内存池中的空间时。由于内存池分配出的内存并没有进行初始化,故如果是自定义类型的对象,则需要显式调用构造函数对内存池的空间进行初始化,此时就需要用到我们的定位new表达式了。

        定位new的使用格式如下:

        new  (place_address)  type (initializer-list)

        其中place_address必须是个指针,而initializer-list就是传递给构造函数的初始化列表,具体使用方法如下:

// 定位new的使用
int main()
{
	//p1现在指向的只是与A对象相同大小的一段空间,并没有调用构造函数初始化对象
	A* p1 = (A*)malloc(sizeof(A));
	new(p1)A(10); //使用定位new显式调用构造函数
	p1->~A(); //显式调用析构函数
	free(p1);

	//operator new底层也是用malloc实现的,只是开辟了空间,并没有调用构造函数
	A* p2 = (A*)operator new(sizeof(A)); 
	new(p2)A(10); //使用定位new显式调用构造函数
	p2->~A();
	operator delete(p2);
	return 0;
}

我们可以发现,operator new再加上我们的定位new之后就相当于new操作符。operator new函数先申请空间,然后再使用定位new调用构造函数进行初始化。

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

        讲了这么多,我们也知道了如何在C++中new到一个对象了【C++深入浅出】C/C++内存管理(教你如何new到对象),C++深入浅出,c++,开发语言,内存管理,堆,new和delete 最后我们就来总结一下C语言和C++动态申请的区别叭,作为找到对象之后的祝福叭【C++深入浅出】C/C++内存管理(教你如何new到对象),C++深入浅出,c++,开发语言,内存管理,堆,new和delete

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

以上,就是本期的全部内容啦🌸

制作不易,能否点个赞再走呢🙏文章来源地址https://www.toymoban.com/news/detail-713297.html

到了这里,关于【C++深入浅出】C/C++内存管理(教你如何new到对象)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 大聪明教你学Java | 深入浅出聊 ConcurrentHashMap

    🍊作者简介: 不肯过江东丶,一个来自二线城市的程序员,致力于用“猥琐”办法解决繁琐问题,让复杂的问题变得通俗易懂。 🍊支持作者: 点赞👍、关注💖、留言💌~ 在 Java 的集合框架中,HashMap 是一种非常常用的数据结构,它提供了键值对形式的存储和访问方式。然

    2024年02月10日
    浏览(37)
  • 深入浅出Rust内存安全:构建更安全、高效的系统应用

    在过去几年中,Rust编程语言以其独特的安全保障特性和高效的性能,成为了众多开发者和大型科技公司的新宠。尤其是其内存安全特性,成为了广泛讨论和赞扬的焦点。本文旨在深入探讨内存安全的概念、Rust在内存安全方面的独到之处,以及这些特性对系统开发的深远影响

    2024年02月19日
    浏览(47)
  • 【C++深入浅出】模版初识

    目录 一. 前言 二. 泛型编程 三. 函数模版  3.1 函数模版的概念 3.2 函数模版的格式 3.3 函数模版的原理 3.4 函数模板的实例化 3.5 模板参数的匹配原则 四. 类模版 4.1 类模版的定义 4.2 类模版的实例化         本期我们要介绍的是C++的又一大重要功能---- 模版 。通过模版,我们

    2024年02月08日
    浏览(54)
  • 深入浅出C++ ——线程库

      在C++11之前,涉及到多线程问题,都是和平台相关的,比如windows和linux下各有自己的接口,这使得代码的可移植性比较差。C++11中最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库,而且在原子操作中还引入了 原子类 的概念。要使用标准库中

    2024年02月03日
    浏览(63)
  • 深入浅出C++——C++的类型转换

    在C语言中,如果 赋值运算符左右两侧类型不同 ,或者形参与实参类型不匹配,或者 返回值类型与接收返回值类型不一致 时,就需要发生类型转化。 C语言中总共有两种形式的类型转换: 隐式类型转换:编译器在编译阶段自动进行转换,不能转就编译失败。 显式类型转换:

    2024年02月07日
    浏览(50)
  • 深入浅出C++ ——C++11

        1998年是C++标准委员会成立的第一年,本来计划以后每5年视实际需要更新一次标准,C++国际标准委员会在研究C++ 03的下一个版本的时候,一开始计划是2007年发布,所以最初这个标准叫C++07。但是到06年的时候,官方觉得2007年肯定完不成C++ 07,而且官方觉得2008年可能也

    2024年02月02日
    浏览(50)
  • 【C++深入浅出】类和对象下篇

            老样子,先来回顾一下上期的内容:上期我们着重学了C++类中的六大 默认成员函数 ,并自己动手实现了一个 日期类 ,相信各位对C++中的类已经有了一定程度的了解。本期就是类和对象的最后一篇啦,终于要结束咯,吧唧吧唧         话不多说,开吃咯!!! 2.1

    2024年02月08日
    浏览(55)
  • 【C++深入浅出】初识C++中篇(引用、内联函数)

      目录 一. 前言 二. 引用 2.1 引用的概念 2.2 引用的使用 2.3 引用的特性 2.4 常引用 2.5 引用的使用场景 2.6 传值、传引用效率比较 2.7 引用和指针的区别  三. 内联函数 3.1 内联函数的概念 3.2 内联函数的特性          上期说道,C++是在C的基础之上,容纳进去了 面向对象编程

    2024年02月12日
    浏览(137)
  • 【C++】模板初阶 【 深入浅出理解 模板 】

    如何实现一个通用的交换函数呢? 使用函数重载虽然可以实现 ,但是有一下几个不好的地方: 重载的函数 仅仅是类型不同 ,代码复用率比较低, 只要有新类型出现时,就需要用户自己增加对应的函数 代码的可维护性比较低,一个出错可能所有的重载均出错 那能否 告诉编

    2024年02月05日
    浏览(54)
  • 【C++深入浅出】日期类的实现

    目录 一. 前言  二. 日期类的框架 三. 日期类的实现 3.1 构造函数 3.2 析构函数 3.3 赋值运算符重载 3.4 关系运算符重载 3.5 日期 +/- 天数 3.6 自增与自减运算符重载 3.7 日期 - 日期 四. 完整代码          通过前面两期类和对象的学习,我们已经对C++的类有了一定的了解。本期我

    2024年02月07日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包