以下编译调试环境为vs2017
一:总述
如果不涉及到高级,只是从简单应用层面来讲,我们只会使用new,delete也基本上够日常开发了,也就是说,常规情况下,我们对内存的使用、管理上,可能不需要做太多;
但是,作为内存高级话题,我希望在原有基础之上,讲解一些新东西,让大家对内存有一个更深入的了解,因为这些高级内存知识的储备,对我们理解其他的c++话题特别有帮助,比如理解模板中的内存分配等等,同时,实战中避免不了看到很多内存有关的高级用法,接触到一些工程中常用的概念比如内存池等等,这也是讲解本话题的主要目的;
----------------
二:从new说起
(2.1)new类对象时加不加括号的差别
class A
{
public:
};
我定义了一个空类,main中代码如下:
A *pa = new A();
A *pa2 = new A;
可能很多朋友会疑惑这两行代码有啥区别?我可以分几方面来说:
(1)如果是个空类,则没啥区别;当然现实中,你也不会写个空类
(2)类A中有成员变量
int m_i;
我们观察:
A *pa = new A(); //带括号这个 ,m_i被初始化为0.
A *pa2 = new A; //这里,m_i是随机值
这说明,带括号这种初始化会把一些和成员变量有关的内存清理为0,但不是整个对象的内存全部清零,为什么我这么说,如果真想探究我这么说的理由,欢迎观看我的《c++对象模型探索》,这是一门极其值得看的课程,我极力推荐;
(3)如果类中有构造函数;增加如下构造函数:
A()
{
}
你会发现,
A *pa = new A();
A *pa2 = new A;
行为一样了,new A()这种,成员变量有关内存也不清0了,看这意思是成员变量诸如m_i的初始化工作要转交给类A的构造函数干了,而这个构造函数里啥也没有,所以,m_i没有被初始化。
如果大家不写自己的构造函数,那么有些情况下系统会帮助我们产生构造函数,但是当前这种情况下,如果我们没有写自己的构造函数,系统是不帮助我们产生构造函数的,至于什么情况下系统会帮助我们产生构造函数,那么推荐观看我的课程《c++对象模型探索》;在做这门课程中,我也有更深的体会和进步,怎么说呢?凡是搞c++的朋友,如果不把这门《c++对象模型探索》课程看了,绝对算得上是自己职业生涯中的一大遗憾,当然,这仅仅代表我个人的观点,只供参考。
(4)不同的看上去的感觉:
其实大家也看出来了,new A()这种写法它就类似于函数调用,这里就相当于调用了一个无参的构造函数这种写法,类名,屁股后边跟个小括号。
new A; 这种呢?这种语法格式,当然不像是函数调用,但实际上他也是调用默认的构造函数的;
其实这只是一种看上去的感觉,两者在这个角度没有啥太大区别;
----
那
int *p3 = new int;
int *p = new int();
int *p2 = new int(100);
有什么差别吗?这种是简单类型,差别主要还是在初值上
第一行,初值为随机值;
第二行,初始化为0;
第三行,初值为100;
------------------
(2.2)new干了啥
A *pa = new A();
这代码都会写,但new是个啥东西?new你可以把它叫成关键字,也可以叫成操作符,都可以,在vs2017里你按一下f12,你会发现operator new这种字样,
_Ret_notnull_ _Post_writable_byte_size_(_Size)
_VCRT_ALLOCATOR void* __CRTDECL operator new(
size_t _Size
);
所以,我们把它叫成操作符是没毛病的;
我这里带着大家看一看,传统的new操作符干了啥事;
我们掐个断点,反汇编看一下:
我们发现new主要干了两个事,一个是调用operator new,一个是调用构造函数;
我们可以继续往里跟踪一点,发现operator new调用了一个malloc :
00036658 call _malloc (0313F2h)
那有些同学会问,operator new是啥?
operator new真是一个函数
我随便写一行:
operator new(12);
按F12,大家会发现,这东西是个函数,在他的代码中,他调用了malloc:
if (void* const block = malloc(size))
我们根据这些线索,可以画出一个new的大概调用关系出来;我习惯于缩进四格来表现调用关系(感觉比图形看起来清晰,图形看起来太乱了),如果再往里调用再缩进四格,如此反复:
A *pa = new A(); //操作符
operator new () //函数
malloc() //c风格函数
A::A (03116Dh)
有分配内存,必然有释放内存,所以学习了上边几个东西之后,大家也不难想象,应该有对应的释放内存的函数,大家注意这个释放的调用顺序
我们掐个断点到如下delete pa;跟踪到里边去看看他的调用;
delete pa;
A::~A(); //如果有析构函数,则先调用析构函数,如果没有,就拉倒;
operator delete();
free(); //c风格函数
所以大家将来面试如果被问到,new和malloc的区别,大家必须要答上,new一个对象的时候,不但分配内存,而且还会调用类的构造函数(当然如果类没有构造函数,系统也没有给类生成构造函数,那不好意思,new就没法调用构造函数了);另外大家刚才也看到了,在某些情况下A *pa = new A();,可以把某些对象的部分内存数据清0,malloc也没这个能力,这个是new的能力之一;
---------------
(2.3)malloc干了啥
大家上边看到了,其实,new最终是通过调用malloc来分配内存的,这一点大家务必要有认知;
那malloc是怎么分配内存的,这个很复杂(它内部有各种链表,链来链去,又要记录分配,当你释放内存的时候如果相邻的有空闲内存块,又要把空闲内存块回收再一起等等),可能不同的操作系统有不同的干法,malloc可能需要调用跟操作系统有关的更底层的函数来实现内存分配和管理,malloc()是跨平台,通用的函数,但是malloc再往深入探究,代码就不通用了,这不是我们要研究的,我们的认知就到最终是通过malloc来分配就可以了;
-----------
(2.4)总结
如果大家对operator new (),operator delete()这种 函数,malloc(),free()这种c风格函数的直接调用有兴趣,这些大家可以自己写测试代码,因为这些内容的直接调用用到的机会比较少,我就不带着大家一起写了,这些测试代码一般是满足大家的猎奇心理;
一般来讲,我们写c++程序,多数还是提倡用new,delete,不提倡用malloc()和free()这种风格。当然,后续我还会讲解到new也有很多种用法,但是在这里,大家先掌握一般情况下new的用法就可以了;后续再说;
你听懂了吗?