从C++New⼀个类的疑问到堆和栈的问题
我们在敲代码的时候很容易遇到new和delete的应⽤,最近频繁运⽤new⼀个类,便查阅相关资料作为总结和学习。
对象的操作有⽆new的 区别
A a;// a存在栈上
A* a =new a();// a存在堆中
以上两种⽅式皆可实现类的实例化,有⽆new的区别在于:
1 前者在栈中分配内存,后者在堆中分配内存
2 动态内存分配会使对象的可控性增强
3 ⼤程序⽤new,⼩程序不加new,直接申请
4 new必须delete删除,不⽤new系统会⾃动回收内存
⼀ new创建类对象与不⽤new区别
下⾯是总结的⼀些关于new创建类对象特点:构造函数可以被重载
1,new创建类对象需要指针接收,⼀处初始化,多处使⽤
2,new创建类对象使⽤完需delete销毁
3,new创建对象直接使⽤堆空间,⽽局部不⽤new定义类对象则使⽤栈空间
4,new对象指针⽤途⼴泛,⽐如作为函数返回值、函数参数等
5,频繁调⽤场合并不适合new,就像new申请和释放内存⼀样
⼆ new创建类对象实例
1、new创建类对象例⼦:
CTest* pTest =new CTest();
delete pTest;
pTest⽤来接收类对象指针。
new申请的对象,则只有调⽤到delete时再会执⾏析构函数,如果程序退出⽽没有执⾏delete则会造成内存泄漏。
2、只定义类指针
这跟不⽤new声明对象有很⼤区别,类指针可以先⾏定义,但类指针只是个通⽤指针,在new之前并为该类对象分配任何内存空间。⽐如:CTest* pTest =NULL;
但使⽤普通⽅式创建的类对象(CTest pTest;),在创建之初就已经分配了内存空间。⽽类指针,如果未经过对象初始化,则不需要delete 释放。
3、new对象指针作为函数参数和返回值
主要⽰意⼀下类指针对象作为返回值和参数使⽤。
class CTest {public:int a;};
class CBest {public:int b;};
CTest*fun(CBest* pBest){
CTest* pTest =new CTest();
pTest->a = pBest->b;return pTest;
}
int main(){
CBest* pBest =new CBest();
CTest* pRes=fun(pBest);
if(pBest!=NULL)
delete pBest;
if(pRes!=NULL)
delete pRes ;
return0;
}
三 不⽤new创建类对象实例
不⽤new,直接使⽤类定义声明:
CTest mTest;
此种创建⽅式,使⽤完后不需要⼿动释放,该类析构函数会⾃动执⾏。
new和delete是C++关键字,需要编译器⽀持,new的作⽤是分配内存和调⽤类的构造函数,⽽delete是调⽤类的析构函数和释放内存。
关于new和malloc的区别:new 建⽴的是⼀个对象,malloc分配的是⼀块内存。
1. 申请的内存位置
new操作符从⾃由存储区上为对象动态分配内存空间,⽽malloc函数从堆上动态分配内存。⾃由存储区是C++基于new操作符的⼀个抽象概念,凡是通过new操作符进⾏内存申请,该内存即为⾃由存储区。⽽堆是是操作系统所维护的⼀块特殊内存,⽤于程序的内存动态分配,C语⾔使⽤malloc从堆上分配内存,使⽤free释放已分配的对应内存。
那么⾃由存储区是否能够是堆(问题等价于new是否能在堆上动态分配内存),这取决于operator new 的实现细节。⾃由存储区不仅可以是堆,还可以是静态存储区,这都看operator new在哪⾥为对象分配内存。
特别的,new甚⾄可以不为对象分配内存!定位new的功能可以办到这⼀点:
2.返回类型安全性
new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,⽆须进⾏类型转换,故new是符合类型安全性的操作符。⽽malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
类型安全很⼤程度上可以等价于内存安全,类型安全的代码不会试图⽅法⾃⼰没被授权的内存区域。关于C++的类型安全性可说的⼜有很多了。
3.内存分配失败时的返回值
new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL;malloc分配内存失败时返回NULL。
在使⽤C语⾔时,我们习惯在malloc分配内存后判断分配是否成功:
int*a =(int*)malloc (sizeof(int));
if(NULL== a)
{
...
}
else
{
...
}
Note:在使⽤malloc分配内存时,⼀定要加上sizeof,之前遇到过⼀个问题是没有加sizeof,结果在x64系统上正常运⾏没有报错,但是在arm系统上程序崩溃。后⾯百度了⼀下原因但是没有到⽐较专业性的解答。有⽐较了解的欢迎给出答案。
从C语⾔⾛⼊C++阵营的新⼿可能会把这个习惯带⼊C++:
int* a =new int();
if(NULL== a)
{
...
}
else
{
...
}
实际上这样做⼀点意义也没有,因为new根本不会返回NULL,⽽且程序能够执⾏到if语句已经说明内存分配成功了,如果失败早就抛异常了。正确的做法应该是使⽤异常机制:
try
{
int*a =new int();
}
catch(bad_alloc)
{
...
}
4.是否需要指定内存⼤⼩
使⽤new操作符申请内存分配时⽆须指定内存块的⼤⼩,编译器会根据类型信息⾃⾏计算,⽽malloc则需要显式地指出所需内存的尺⼨。
class A{…}
A * ptr = new A;
A * ptr = (A *)malloc(sizeof(A)); //需要显式指定所需内存⼤⼩sizeof(A);
当然了,我这⾥使⽤malloc来为我们⾃定义类型分配内存是不怎么合适的,请看下⼀条。
5.是否调⽤构造函数/析构函数
使⽤new操作符来分配对象内存时会经历三个步骤:
第⼀步:调⽤operator new 函数(对于数组是operator new[])分配⼀块⾜够⼤的,原始的,未命名的内存空间以便存储特定类型的对象。
第⼆步:编译器运⾏相应的构造函数以构造对象,并为其传⼊初值。
第三部:对象构造完成后,返回⼀个指向该对象的指针。
使⽤delete操作符来释放对象内存时会经历两个步骤:
第⼀步:调⽤对象的析构函数。
第⼆步:编译器调⽤operator delete(或operator delete[])函数释放内存空间。
总之来说,new/delete会调⽤对象的构造函数/析构函数以完成对象的构造/析构。⽽malloc则不会。
class A
{
public:
A():a(1),b(1.11){}
private:
int a;
double b;
};
int main()
{
A * ptr =(A*)malloc(sizeof(A));
return0;
}
在return处设置断点,观看ptr所指内存的内容:
可以看出A的默认构造函数并没有被调⽤,因为数据成员a,b的值并没有得到初始化,这也是上⾯我为什么说使⽤malloc/free来处理C++的⾃定义类型不合适,其实不⽌⾃定义类型,标准库中凡是需要构造/析构的类型通通不合适。
⽽使⽤new来分配对象时:
int main()
{
A * ptr =new A;
}
查看程序⽣成的汇编代码可以发现,A的默认构造函数被调⽤了:
6.对数组的处理
C++提供了new[]与delete[]来专门处理数组类型:
A * ptr = new A[10];//分配10个A对象
使⽤new[]分配的内存必须使⽤delete[]进⾏释放:
delete [] ptr;
new对数组的⽀持体现在它会分别调⽤构造函数函数初始化每⼀个数组元素,释放对象时为每个对象调⽤析构函数。注意delete[]要与
new[]配套使⽤,不然会出数组对象部分释放的现象,造成内存泄漏。
⾄于malloc,它并知道你在这块内存上要放的数组还是啥别的东西,反正它就给你⼀块原始的内存,在给你个内存的地址就完事。所以如果要动态分配⼀个数组的内存,还需要我们⼿动⾃定数组的⼤⼩:
int * ptr = (int *) malloc( sizeof(int) );//分配⼀个10个int元素的数组
operator new /operator delete的实现可以基于malloc,⽽malloc的实现不可以去调⽤new。下⾯是编写operator new /operator delete 的⼀种简单⽅式,其他版本也与之类似:
void*operator new(sieze_t size)
{
if(void* mem =malloc(size)
return mem;
else
throw bad_alloc();
}
void operator delete(void*mem)noexcept
{
free(mem);
}
8.是否可以被重载
opeartor new /operator delete可以被重载。标准库是定义了operator new函数和operator delete函数的8个重载版本:
//这些版本可能抛出异常
void*operator new(size_t);
void*operator new[](size_t);
void*operator delete(void*)noexcept;
void*operator delete[](void*0)noexcept;
//这些版本承诺不抛出异常
void*operator new(size_t ,nothrow_t&)noexcept;
void*operator new[](size_t, nothrow_t&);
void*operator delete(void*,nothrow_t&)noexcept;
void*operator delete[](void*0,nothrow_t&)noexcept;
我们可以⾃定义上⾯函数版本中的任意⼀个,前提是⾃定义版本必须位于全局作⽤域或者类作⽤域中。太细节的东西不在这⾥讲述,总之,我们知道我们有⾜够的⾃由去重载operator new /operator delete ,以决定我们的new与delete如何为对象分配内存,如何回收对象。
⽽malloc/free并不允许重载。
9. 能够直观地重新分配内存
使⽤malloc分配的内存后,如果在使⽤过程中发现内存不⾜,可以使⽤realloc函数进⾏内存重新分配实现内存的扩充。realloc先判断当前的指针所指内存是否有⾜够的连续空间,如果有,原地扩⼤可分配的内存地址,并且返回原来的地址指针;如果空间不够,先按照新指定的⼤⼩分配空间,将原有数据从头到尾拷贝到新分配的内存区域,⽽后释放原来的内存区域。
new没有这样直观的配套设施来扩充内存。
10. 客户处理内存分配不⾜
在operator new抛出异常以反映⼀个未获得满⾜的需求之前,它会先调⽤⼀个⽤户指定的错误处理函数,这就是new-handler。
new_handler是⼀个指针类型:
namespace std
{
typedef void(*new_handler)();
}
指向了⼀个没有参数没有返回值的函数,即为错误处理函数。为了指定错误处理函数,客户需要调⽤set_new_handler,这是⼀个声明于的⼀个标准库函数:
namespace std
{
new_handler set_new_handler(new_handler p )throw();
}
set_new_handler的参数为new_handler指针,指向了operator new ⽆法分配⾜够内存时该调⽤的函数。其返回值也是个指针,指向set_new_handler被调⽤前正在执⾏(但马上就要发⽣替换)的那个new_handler函数。
对于malloc,客户并不能够去编程决定内存不⾜以分配时要⼲什么事,只能看着malloc返回NULL。
⼀个由C/C++编译的程序占⽤的内存分为以下⼏个部分
1、栈区(stack)— 由编译器⾃动分配释放 ,存放函数的参数值,局部变量的值等。其
操作⽅式类似于数据结构中的栈。
2、堆区(heap) — ⼀般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回
收 。注意它与数据结构中的堆是两回事,分配⽅式倒是类似于链表,呵呵。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在⼀块的,初始化的
全局变量和静态变量在⼀块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另
⼀块区域。 - 程序结束后由系统释放。
4、⽂字常量区 —常量字符串就是放在这⾥的。 程序结束后由系统释放
5、程序代码区—存放函数体的⼆进制代码。
⼆、例⼦
//main.cpp
int a =0;全局初始化区
char*p1;全局未初始化区
main()
{
int b;栈
char s[]="abc";栈
char*p2;栈
char*p3 ="123456";123456/0在常量区,p3在栈上。
static int c =0;全局(静态)初始化区
p1 =(char*)malloc(10);
p2 =(char*)malloc(20);
分配得来得10和20字节的区域就在堆区。
strcpy(p1,"123456");123456/0放在常量区,编译器可能会将它与p3所指向的"123456"
优化成⼀个地⽅。
}
⼆、堆和栈的理论知识
2.1申请⽅式
stack:
由系统⾃动分配。 例如,声明在函数中⼀个局部变量 int b; 系统⾃动在栈中为b开辟空
间
heap:
需要程序员⾃⼰申请,并指明⼤⼩,在c中malloc函数
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论