new申请的是堆空间 (那应该是远堆还是近堆呢?)
远堆、近堆源于硬件CPU的位数。而且这属于CRuntime库设计的范围内考虑,且这不是C++中的语法。
对于16位CPU来说,一般来说段地址寄存器是16位的,所以2的16次方就是64K。在这个大小内,一般申请的内存都是在程序的同一段内存内的,访问这块内存只用段寄存器就够了。如果很大的话,就要用段地址+偏移地址。就是远堆了。
对于现在的CPU几乎都不考虑这个了,大多都是32位的寄存器或者是64位的了,所以表面上也不分远近了。
其实对于我们平常使用的new一般我们叫他们"new operator"或称为"new expression",它总体上干了三件事情(首先明确new是C++中的一个关键字)
1.获得一块内存空间
2.调用构造函数(如果是创建简单的变量,比如int,short等数据类型的变量都叫做简单类型变量,这样的话此步骤将会被省略)
3.返回正确的指针
请看下面一段代码,之后解说一下
#include <stdio.h>
#include <new.h>
#include <malloc.h>
class DemoA
{
int i;
public:
DemoA(int _i) :i(_i*_i)
{
}
void Say()
{
printf("i=%d\n", i);
}
};
class _DemoA
{
int i;
public:
_DemoA(int _i) :i(_i*_i)
{
}
// 主要是模仿整个new的所有步骤
static _DemoA* mallocDemoA()
{
_DemoA* _pa =NULL;
_pa= (_DemoA*)malloc(sizeof(_DemoA)); // == operator new
_pa->_DemoA::_DemoA(3); // == placement new
return _pa;
}
void Say()
{
printf("i=%d\n", i);
}
};
void main()
{
// "new operator"或称为"new expression"
DemoA* pa = new DemoA(3);
// 拆解new的步骤
_DemoA* _pa = _DemoA::mallocDemoA();
pa->Say();
_pa->Say();
}
这一小段程序,说明了new的内部还是通过C语言的函数malloc来实现的,DemoA和_DemoA类进行对比他们都实现了new完成的功能1.获得内存空间2.调用构造函数3.返回正确的指针,下面用一幅图片来描述整个调试的过程,就能清晰的明白了
也许就目前这个demo说明不了什么,但是实际上new的实现并不仅仅像mallocDemoA函数里边那么几个表达式还有很多代码设计在里边,从经验上讲malloc在分配内存失败时,他是不会调用内存分配失败的处理程序的,但是new就不同了,如果new分配内存失败,他就会调用相应的处理程序。如果是使用VC++6.0跟踪new是跟踪不进去的,但是VS2010是可以的,你可以清晰的看见其内部的逻辑设计,但是能不能看懂就另说了,本质上VC++6.0也是可以跟踪的,只不过VC++6.0是以汇编语言的形式给出的new的过程
在C++的语法规定中,new细化有三种说法
1."new operator"或称为"new expression"
2.operator new
3.placement new
在上面的demo中,main函数中的new操作符就是new operator分支类型,new operator的第一步分配内存实际上就是调用的operator new来完成的,这里operate new中的new就像 + 、-、*、/ 等基本的运算符一样,是可以重载的。operator new默认情况下首先调用内存分配的代码,尝试得到对上的一段空间,如果成功则返回,如果失败,则转去调用new_hander,然后继续重复前面的过程。如果operator new不能满足我们的操作要求,我们就重载它,一下是一个demo
#include <stdio.h>
#include <new.h>
#include <malloc.h>
class DemoB
{
public:
DemoB()
{
}
void* operator new(size_t size)
{
printf("operator new called\n");
return ::operator new(size);
}
};
void main()
{
// operator new
DemoB* pb = new DemoB();
}
之前写的时候是没有DemoB的,后来把它给加上了,因为加上它就能给清楚的理解上一个demo了,下面还是提供贴图展示出跟踪的记录
仔细看一下,重载的operator new里边还是调用的系统的::operator new,其实对于operator new的重载并不一定在类中,全局中也可以重载operator new,但是这时在函数里边就不能在递归调用operator new了,而只能调用C语言中的malloc函数下面是一个demo
#include <stdio.h>
#include <new.h>
#include <malloc.h>
class DemoB
{
public:
DemoB()
{
}
};
void* operator new(size_t size)
{
printf("operator new called\n");
return ::operator new(size);
}
void main()
{
// operator new
DemoB* pb = new DemoB();
}
贴图分析 跟踪的代码
程序一直在递归,出不去了,下面在看一个demo
#include <stdio.h>
#include <new.h>
#include <malloc.h>
class DemoB
{
public:
DemoB()
{
}
};
void* operator new(size_t size)
{
printf("operator new called\n");
return malloc(size);//return ::operator new(size);
}
void main()
{
// operator new
DemoB* pb = new DemoB();
}
贴图分析 跟踪的代码
这是程序是可以通过的而且正常
下面说一下关于placement new分支类型,placement new分支类型是实现定位构造的,因此其对应的是new operator三步当中的第二步,也就是在取得了一块可以容纳指定类型的内存后,在这块内存上构造一个对象,下面的程序中mallocDemoA()函数中的_pa->DemoA::DemoA(3); 并不是标准的写法,应该用placement new才是正宗的 呵呵。
#include <stdio.h>
#include <new.h>
#include <malloc.h>
class DemoA
{
int i;
public:
DemoA(int _i) :i(_i*_i)
{
}
static DemoA* mallocDemoA()
{
DemoA* _pa =NULL;
_pa= (DemoA*)malloc(sizeof(DemoA)); // == operator new
_pa->DemoA::DemoA(3); // == placement new
return _pa;
}
};
class DemoC
{
int i;
public:
DemoC(int _i_) :i(_i_ * _i_)
{
}
~DemoC()
{
}
};
void main()
{
// "new operator"或称为"new expression"
DemoA* pa = new DemoA(3);
DemoA* _pa = DemoA::mallocDemoA();
// placement new
char str[sizeof(DemoC)];
DemoC* pc = (DemoC*)str;
new(pc) DemoC(3); // _pc->DemoA::DemoA(3);
pc->~DemoC(); /* 只有placement new操作时是必须显示调用类的析构函数的
这也是显式调用析构函数的唯一情况
*/
}
下面的贴图并没有展示调试的过程,相信看过上面的一些调试跟踪图片,这里是不难理解的,这里仅仅着重强调了,析构函数仅仅是在这个地方用的以及与mallocDemoA()对应的地方
对头文件<new>或<new.h>的引用是必须的,这样才可以使用placement new。这里"new(pc) DemoC(3)"这种奇怪的写法便是placement new,它实现了在指定内存地址上用指定类型的构造函数来构造一个对象的功能,后面DemoC(3)就是对构造函数的显式调用。这里不难发现,这块指定的地址既可以是栈,又可以是堆,placement对此不加区分。但是,除非特别必要,不要直接使用placement new ,这毕竟不是用来构造对象的正式写法,只不过是new operator的一个步骤而已。使用new
operator编译器会自动生成对placement new的调用的代码,因此也会相应的生成使用delete时调用析构函数的代码。如果是像上面那样在栈上使用了placement new,则必须手工调用析构函数,这也是显式调用析构函数的唯一情况:pc->~DemoC();
当我们觉得默认的new operator对内存的管理不能满足我们的需要,而希望自己手工的管理内存时,placement new就有用了;
STL中的allocator就使用了这种方式,借助placement new来实现更灵活有效的内存管理;
总结一下上面的代码,最终汇到一个程序中。它。。。。。是这个样子的
#include <stdio.h>
#include <new.h>
#include <malloc.h>
class DemoA
{
int i;
public:
DemoA(int _i) :i(_i*_i)
{
}
static DemoA* mallocDemoA()
{
DemoA* _pa =NULL;
_pa= (DemoA*)malloc(sizeof(DemoA)); // == operator new
_pa->DemoA::DemoA(3); // == placement new
return _pa;
}
void Say()
{
printf("i=%d\n", i);
}
};
class DemoB
{
public:
void* operator new(size_t size)
{
printf("operator new called\n");
return ::operator new(size);
}
};
class DemoC
{
int i;
public:
DemoC(int _i_) :i(_i_ * _i_)
{
}
~DemoC()
{
}
};
class DemoD
{
int a;
public:
DemoD()
{
printf("ctor\n");
}
~DemoD()
{
printf("dtor\n");
}
void* operator new[](size_t size)
{
void* p = operator new(size);
printf("size=%d address=%p\n",size, p);
return p;
}
};
void main()
{
// "new operator"或称为"new expression"
DemoA* pa = new DemoA(3);
// ||
// ||
DemoA* _pa = DemoA::mallocDemoA();
// operator new
DemoB* pb = new DemoB();
// placement new
char str[sizeof(DemoC)];
DemoC* pc = (DemoC*)str;
new(pc) DemoC(3); // pc->DemoC::DemoC(3);
pc->Say();
pc->~DemoC();// 只有placement new操作时是必须显示调用类的析构函数的 ||这也是显式调用析构函数的唯一情况
// operator new[] 与 operator new
DemoD* mc = new DemoD[3];
printf("address of mc=%p\n", mc);
delete[] mc;
printf("\n\n");
}
至此,还没有阐述delete 是根据什么东东来释放内存的,时间有限,以后再说吧