一、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 堆和栈的更多相关文章

  1. 栈 堆 stack heap 堆内存 栈内存 内存分配中的堆和栈 掌握堆内存的权柄就是返回的指针 栈是面向线程的而堆是面向进程的。 new/delete and malloc/ free 指针与内存模型

    小结: 1.栈内存 为什么快? Due to this nature, the process of storing and retrieving data from the stack is ver ...

  2. JVM学习(2)——技术文章里常说的堆,栈,堆栈到底是什么,从os的角度总结

    俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 堆栈是栈 JVM栈和本地方法栈划分 Java中的堆,栈和c/c++中的堆,栈 数据结构层面的堆,栈 os层面 ...

  3. C语言堆栈入门——堆和栈的区别

    来看一个网上很流行的经典例子: main.cpp int a = 0; 全局初始化区 char *p1; 全局未初始化区 main() { int b; 栈 char s[] = "abc& ...

  4. iOS开发中的内存分配(堆和栈)

    进程的内存分区 所有进程(执行的程序)都必须占用一定数量的内存,它或是用来存放从磁盘载入的程序代码,或是存放取自用户输入的数据等等.不过进程对这些内存的管理方式因内存用途不一而不尽相同,有些内存是事先 ...

  5. C++内存分配方式详解——堆、栈、自由存储区、全局/静态存储区和常量存储区

    栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区.里面的变量通常是局部变量.函数参数等.在一个进程中,位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数的调用.和堆一样 ...

  6. (十一)C语言中内存堆和栈的区别

    在计算机领域,堆栈是一个不容忽视的概念,我们编写的C语言程序基本上都要用到.但对于很多的初学着来说,堆栈是一个很模糊的概念. 堆栈:一种数据结构.一个在程序运行时用于存放的地方,这可能是很多初学者的认 ...

  7. 转:内存区划分、内存分配、常量存储区、堆、栈、自由存储区、全局区[C++][内存管理][转载]

    内存区划分.内存分配.常量存储区.堆.栈.自由存储区.全局区[C++][内存管理][转载] 一. 在c中分为这几个存储区1.栈 - 由编译器自动分配释放2.堆 - 一般由程序员分配释放,若程序员不释放 ...

  8. C/C++中堆与栈

    本文介绍C/C++中堆,栈及静态数据区.   五大内存分区 在C++中,内存分成5个区,他们分别是堆.栈.自由存储区.全局/静态存储区和常量存储区.下面分别来介绍: 栈,就是那些由编译器在需要的时候分 ...

  9. C语言中的堆与栈20160604

    首先声明这里说的是C语言中的堆与栈,并不是数据结构中的!一.前言介绍:C语言程序经过编译连接后形成编译.连接后形成的二进制映像文件是静态区域由代码段和数据段(由二部分部分组成:只读数据 段,未初始化数 ...

随机推荐

  1. 正确计算linux系统内存使用率

    参考:https://blog.gesha.net/archives/406/ 图中的例子很典型,就是:多数的linux系统在free命令后会发现free(剩余)的内存很少,而自己又没有开过多的程序或 ...

  2. 10.Solr4.10.3数据导入(DIH全量增量同步Mysql数据)

    转载请出自出处:http://www.cnblogs.com/hd3013779515/ 1.创建MySQL数据 create database solr; use solr; DROP TABLE ...

  3. HTML标签之marquee

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/zkn_CS_DN_2013/article/details/25229719 <html> ...

  4. WCF简单实例--用Winform启动和引用

    以订票为例简单应用wcf程序,需要的朋友可以参考下 本篇转自百度文档,自己试过,确实可以用. 以订票为例简单应用wcf 新建一个wcf服务应用程序 在IService1.cs定义服务契约 namesp ...

  5. PHP错误提示的关闭方法详解

    关闭PHP错误脚本提示是程序上线了必须做的一件事情,就是不管程序怎么报错我们都不能让错误日志在服务器上给大家看到,下面我来总结两种关闭PHP错误脚本提示的具体方法     最简单的办法就是直接在php ...

  6. oneinstack远程管理数据库

    本篇文章主要内容是本地工具连接数据非网页(网站)连接 如果你想使用网页(网站)连接远程数据库,请看下面的官网教程 OneinStack如何配置MySQL远程连接? 为了安全考虑,OneinStack仅 ...

  7. opengl渲染管线梳理

    opengl渲染管线梳理 http://www.cnblogs.com/zhanglitong/p/3238989.html 坐标系变换和矩阵 http://www.cppblog.com/guoji ...

  8. 网络嗅探与欺骗(第一二部分)非平台——P201421410029

    中国人民公安大学 Chinese people’ public security university 网络对抗技术 实验报告   实验二 网络嗅探与欺骗     学生姓名 李政浩 年级 2014 区 ...

  9. MP实战系列(九)之集成Shiro

    下面示例是在之前的基础上进行的,大家如果有什么不明白的可以参考MP实战系列的前八章 当然,同时也可以参考MyBatis Plus官方教程 建议如果参考如下教程,使用的技术为spring+mybatis ...

  10. Android开发——Android中常见的4种线程池(保证你能看懂并理解)

    0.前言 转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52415337 使用线程池可以给我们带来很多好处,首先通过线程池中线程的重用 ...