C++ new和delete 堆和栈
一、new和delete基本用法
程序开发中内存的动态分配与管理永远是一个让C++开发者头痛的问题,在C中,一般是通过malloc和free来进行内存分配和回收的,在C++中,new和delete已经完全包含malloc和free的功能,并且更强大、方便、安全。
new一般用法:
new 类型 (初值)
用new分配数组空间时不能指定初值。
delete一般用法:
delete [] 指针变量
[]部分是可选的,当释放数组所占内存时必须加[]。当你对一个指针使用 delete,delete 知道是否有数组大小信息的唯一方法就是由你来告诉它。如果你在你使用的 delete 中加入了方括号,delete 就假设那个指针指向的是一个数组。否则,就假设指向一个单一的对象。
int *i = new int; //没有初始值
int *j = new int(); //初始值为100
int *iArr = new int[]; //分配具有3个元素的数组
delete i; //释放单个变量所占用的内存
delete j;
delete []iArr; //释放数组所占用的内存
一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存。应用程序一般使用malloc,realloc,new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。
二、new/delete和malloc/free的区别
对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。
由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。
C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。new出来的指针是直接带类型信息的。而malloc返回的都是void指针。
例1:
#include <iostream>
#include <malloc.h>
using namespace std;
class myclass
{
public:
myclass()
{
i = ;
}
void myfoo()
{
cout << "i = " << i << endl;
}
private:
int i;
};
int main()
{
myclass *p = new myclass;
myclass *q = (myclass *)malloc(sizeof(myclass));
p->myfoo();
q->myfoo();
delete p;
free(q);
return ;
}
程序执行结果为:
i = 1
i = 0
从上例可看出,new调用了类myclass的构造函数,而malloc只是分配了空间,并没有调用构造函数,因此会出现调用q->myfoo()函数时,输出的结果具有随机性。如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”,理论上讲程序不会出错,但是该程序的可读性很差。所以new/delete必须配对使用,malloc/free也一样。
例2:
#include <iostream>
using namespace std;
class myclass
{
public:
~myclass()
{
cout << "Goodbye" << endl;
}
};
int main()
{
myclass *p = new myclass;
free(p);
return ;
}
上例中,~myclass()为类的析构函数,对象离开作用域或被delete的时候会调用。指针p指向了一个堆上创建的myclass对象,若用free来释放内存,则不会调用析构函数,所以上面的程序没有输出。如将free(p)改为:
delete p
程序执行时将会调用到myclass类的析构函数,输出结果为:
Goodbye
三、new和多维数组
当使用new运算符定义一个多维数组变量或数组对象时,它产生一个指向数组第一个元素的指针,返回的类型保持了除最左边维数外的所有维数。例如:
int *p1 = new int[];
返回的是一个指向int的指针int*。
int (*p2)[] = new int[][];
new了一个二维数组,去掉最左边那一维[3],剩下int[10],所以返回的是一个指向int[10]这种一维数组的指针int (*)[10]。
int (*p3)[][] = new int[][][];
new了一个三维数组, 去掉最左边那一维[5], 还有int[3][10],所以返回的是一个指向二维数组int[2][10]这种类型的指针int (*)[3][10]。
四、内存分配时的出错处理
我们都知道,使用 malloc/calloc 等分配内存的函数时,一定要检查其返回值是否为“空指针”,即检查分配内存的操作是否成功,这是良好的编程习惯,也是编写可靠程序所必需的。但是,如果你简单地把这一招应用到 new 上,那可就不一定正确了。
例如:
int* p = new int[SIZE];
if ( p = = ) // 检查 p 是否空指针
return -;
其实,这里的 if( p == 0 )完全是没意义的。在C++里,如果 new 分配内存失败,默认是抛出异常的。所以,如果分配成功,p == 0 就绝对不会成立;而如果分配失败了,也不会执行 if ( p == 0 ),因为分配失败时,new 就会抛出异常跳过后面的代码。如果你想检查 new 是否成功,应该捕捉异常:
try
{
int* p = new int[SIZE];
}
catch ( const bad_alloc& e )
{
return -;
}
事实上,C++中并非只有抛出异常的new,也有不抛异常的new,即通常所说的“nothrow new”。可以这样使用它:
T* p = new (nothrow) T(MAX_SIZE);
其中,nothrow是头文件<new>中定义的一个类型为std::nothrow_t的常量,我们可以直接使用它。这时,如果内存分配失败,p的值将为空(0),且不会有异常抛出,跟C的malloc很像了。
四、内存分配的“栈”和“堆”
栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等。
堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。
下面通过汇编代码来了解下栈和堆内存的分配:
int main()
{
int* p = new int;
return ;
}
其中,“int* p = new int;
”对应汇编代码为:
0041358E push //分配一个int型数据大小内存(4个字节),相当于call operator new前,参数入栈
call operator new (4111D6h)
add esp, //call operator new后,恢复栈结构
mov dword ptr [ebp-0D4h],eax //eax值给call operator new返回的结果生成一个临时变量
0041359E mov eax,dword ptr [ebp-0D4h] //临时变量的值赋给寄存器eax
004135A4 mov dword ptr [p],eax //寄存器eax值赋给栈上指针p
上面这句代码就涉及了内存分配的堆和栈,new分配了一块堆内存,指针p分配的是一块栈内存,这句代码的意思就是:在栈内存中存放了一个指向一块堆内存的指针p。在程序会先确定在堆中分配内存的大小,然后调用operator new分配内存,调用结束后返回值存入eax中,再将内存的首地址放入栈中(为p赋值)。 
堆和栈主要的区别有以下几点:
(1)管理方式和碎片问题
对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生内存碎片。
(2)分配效率
栈是系统提供的数据结构,计算机会在底层对栈提供支持,分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++提供的,它的机制相对复杂,显然堆的效率比栈要低得多。
下面通过汇编代码分析下栈和堆内存存取的效率。
void main()
{
char a = ;
char c[] = "";
char* p = (char *)malloc();
strcpy(p, c);
a = c[];
a = p[];
return;
}
程序中对堆和栈存取的汇编代码为:
a = c[];
004135E7 mov al,byte ptr [ebp-1Fh]
004135EA mov byte ptr [ebp-],al
a = p[];
004135ED mov eax,dword ptr [ebp-2Ch]
004135F0 mov cl,byte ptr [eax+]
004135F3 mov byte ptr [ebp-],cl
可以看出,在栈上存取时直接就把字符串中的元素读到寄存器al中,在堆上存取时则要先把指针值读到eax中,在根据eax读取字符,显然慢了。
(3)增长方向不同
栈内存由一个栈指针esp来开辟和回收,栈内存是从高地址向低地址增长的,增长时,栈指针向低地址方向移动,指针的地址值也就相应的减小;回收时,栈指针向高地址方向移动,地址值也就增加。所以栈内存的开辟和回收都只是指针的加减。
对于堆来讲,增长方向是向上的,也就是向着内存高地址方向移动;回收时,指针向低地址方向移动,地址值也就减小。
(4)空间大小不同
一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的。无论是堆还是栈,都要防止越界现象的发生,因为越界的结果要么是程序崩溃,要么是摧毁程序的堆、栈结构,产生以想不到的结果。
C++ new和delete 堆和栈的更多相关文章
- 栈 堆 stack heap 堆内存 栈内存 内存分配中的堆和栈 掌握堆内存的权柄就是返回的指针 栈是面向线程的而堆是面向进程的。 new/delete and malloc/ free 指针与内存模型
小结: 1.栈内存 为什么快? Due to this nature, the process of storing and retrieving data from the stack is ver ...
- JVM学习(2)——技术文章里常说的堆,栈,堆栈到底是什么,从os的角度总结
俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 堆栈是栈 JVM栈和本地方法栈划分 Java中的堆,栈和c/c++中的堆,栈 数据结构层面的堆,栈 os层面 ...
- C语言堆栈入门——堆和栈的区别
来看一个网上很流行的经典例子: main.cpp int a = 0; 全局初始化区 char *p1; 全局未初始化区 main() { int b; 栈 char s[] = "abc& ...
- iOS开发中的内存分配(堆和栈)
进程的内存分区 所有进程(执行的程序)都必须占用一定数量的内存,它或是用来存放从磁盘载入的程序代码,或是存放取自用户输入的数据等等.不过进程对这些内存的管理方式因内存用途不一而不尽相同,有些内存是事先 ...
- C++内存分配方式详解——堆、栈、自由存储区、全局/静态存储区和常量存储区
栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区.里面的变量通常是局部变量.函数参数等.在一个进程中,位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数的调用.和堆一样 ...
- (十一)C语言中内存堆和栈的区别
在计算机领域,堆栈是一个不容忽视的概念,我们编写的C语言程序基本上都要用到.但对于很多的初学着来说,堆栈是一个很模糊的概念. 堆栈:一种数据结构.一个在程序运行时用于存放的地方,这可能是很多初学者的认 ...
- 转:内存区划分、内存分配、常量存储区、堆、栈、自由存储区、全局区[C++][内存管理][转载]
内存区划分.内存分配.常量存储区.堆.栈.自由存储区.全局区[C++][内存管理][转载] 一. 在c中分为这几个存储区1.栈 - 由编译器自动分配释放2.堆 - 一般由程序员分配释放,若程序员不释放 ...
- C/C++中堆与栈
本文介绍C/C++中堆,栈及静态数据区. 五大内存分区 在C++中,内存分成5个区,他们分别是堆.栈.自由存储区.全局/静态存储区和常量存储区.下面分别来介绍: 栈,就是那些由编译器在需要的时候分 ...
- C语言中的堆与栈20160604
首先声明这里说的是C语言中的堆与栈,并不是数据结构中的!一.前言介绍:C语言程序经过编译连接后形成编译.连接后形成的二进制映像文件是静态区域由代码段和数据段(由二部分部分组成:只读数据 段,未初始化数 ...
随机推荐
- 通过sqli-labs学习sql注入——基础挑战之less1-3
首先,先看一些基础知识吧!!!!本人只是初学者,记录一下自己的学习过程,有什么错误之处请指出,谢谢!大佬请绕过!!!! url编码:一般的url编码其实就是那个字符的ASCII值得十六进制,再在前面加 ...
- python3: 数字日期和时间(2)
12.基本的日期与时间转换 Q: 你需要执行简单的时间转换,比如天到秒,小时到分钟等的转换 A: 为了执行不同时间单位的转换和计算,请使用 datetime 模块. 比如,为了表示一个时间段,可以创建 ...
- Q矩阵输出
程序启动时: 1.Q矩阵在InitQX中对角阵赋初值为0.25,GPS卫星数6 2.Q矩阵初值在初始化时由GetBL获得,改变Q对角阵 Q初值第0个卫星 10000000000.000 X初值第0个卫 ...
- vultr vps(ubuntu)忘记密码
参考官方解决方案:https://www.vultr.com/docs/boot-into-single-user-mode-reset-root-password 在此仅给出ubuntu下的解决 D ...
- oracle备份与恢复
Oracle数据库有三种标准的备份方法,它们分别是导出/导入(EXP/IMP).热备份和冷备份.导出备件是一种逻辑备份,冷备份和热备份是物理备份. 导出/导入(Export/Import) 利用Exp ...
- NOIP模拟赛-2018.11.6
NOIP模拟赛 今天想着反正高一高二都要考试,那么干脆跟着高二考吧,因为高二的比赛更有技术含量(我自己带的键盘放在这里). 今天考了一套英文题?发现阅读理解还是有一些困难的. T1:有$n$个点,$m ...
- BZOJ1005:[HNOI2008]明明的烦恼(组合数学,Prufer)
Description 自从明明学了树的结构,就对奇怪的树产生了兴趣......给出标号为1到N的点,以及某些点最终的度数,允许在任意两点间连线,可产生多少棵度数满足要求的树? Input 第一行为N ...
- WCF错误:413 Request Entity Too Large 的一个解决方法
在我们用WCF传输数据的时候,如果启用默认配置,传输的数据量过大,经常会出这个WCF:413 Request Entity Too Large的错误. WCF包含服务端与客户端,所以这个错误可能出现在 ...
- Docker技术入门与实战 第二版-学习笔记-8-网络功能network-3-容器访问控制和自定义网桥
1)容器访问控制 容器的访问控制,主要通过 Linux 上的 iptables防火墙来进行管理和实现. iptables是 Linux 上默认的防火墙软件,在大部分发行版中都自带. 容器访问外部网络 ...
- ORA-27125: unable to create shared memory segment的解决方法(转)
ORA-27125: unable to create shared memory segment的解决方法(转) # Kernel sysctl configuration file for Red ...