一、C++中new的三种含义
"new" 是C++的一个关键字,同时也是操作符。C++中的new,至少代表以下三种含义:new operator、operator new、placement new。
(1)new operator就是平时使用的new,例如:使用关键字new在堆上动态创建一个类对象,A *ptr = new A。它实际上做了三件事:获取一块内存空间[operator new实现]、调用构造函数[placement new实现]、返回正确的指针(只是做了一个指针的类型转换,实际上编译出的代码中并不需要这种转换)。
这三个步骤我们不能更改,但是具体到某一步骤中的行为,如果它不满足我们的具体要求时,可以更改它。
(2)调用operator new来分配内存。这里的new是一个操作符(像加减乘除操作符一样),可以重载的。
operator new 默认情况下首先调用分配内存的代码,尝试得到一段堆上的空间。若成功就返回,若失败则调用new_hander,然后继续重复前面过程。如果对这个过程不满意,可以重载operator new,来设置我们希望的行为。
例如:通过::operator new(size)调用原有的全局operator new,实现了在分配内存之前输出一句打印。
全局的operator new也可以被重载,但这样就不能再递归的使用new来分配内存,只能使用malloc了。
相应的delete也有delete operator 和 operator delete,如果operator new被重载,就应该相应的的重载operator delete。
#include <stdio.h>
#include <stdlib.h>
// reload global operator new
void* operator new(size_t size)
{
printf("global new\n");
return malloc(size);
}
// reload global operator new
void operator delete(void *ptr)
{
printf("global delete\n");
free(ptr);
}
class A
{
public:
 void* operator new(size_t size)
   {
       printf("operator new called\n");
       return ::operator new(size);
   }
void operator delete(void *ptr)
{
printf("operator delete called\n");
::operator delete(ptr);
}
void printLine() {printf("----------------------\n");}
};
int main()
{
A *ptrA = new A();
ptrA->printLine();
delete ptrA;
return 0;
}
(3)placement new用来实现定位、构造。在取得了一块可以容纳指定类型对象的内存后,在这块内存上构造一个对象。
必须引用头文件<new>或<new.h>才能使用placement new。new(ptrA) A(4); 实现了在指定内存地址上(指针ptrA所指向的内存地址)用指定类型的构造函数(调用带参数的构造函数A(4))来构造一个对象的功能。必须显示的调用析构函数ptrA->~A();
#include <stdio.h>
#include <new>
class A
{
int i;
public:
A(int tmpI) : i(tmpI * tmpI) {}
void printVar() {printf("i = %d\n", i);}
};
int main()
{
char memA[sizeof(A)];
A *ptrA = (A*)memA;
new(ptrA) A(4); // placement new <=> ptrA->A::A(3)
ptrA->printVar();
ptrA->~A(); // destructor must be called manually
return 0;
}
二、new/delete与new[]/delete[]的区别
// 删除单变量地址空间
int *a = new int;
delete a;
// 删除数组空间
int *a = new int[5];
delete[] a;
// 分配了5个连续的MyClass实例,并依次调用了构造函数。注意:此时MyClass的构造函数是不带参数的
MyClass *mc = new MyClass[5];
delete[] mc;
对动态分配的数组调用delete[]时,其行为根据所申请变量的类型会有所不同。如果指针p指向简单类型(如int、char等),其结果只不过是这块内存被回收,此时delete[]与delete没有区别。
如果指针p指向的是复杂类型,delete[]会针对动态分配得到的每个对象调用析构函数,然后再释放内存。
(1)delete[]是如何知道要为多少个对象调用析构函数的?
#include <stdio.h>
class MyClass
{
int a;
public:
MyClass(){printf("construct\n");}
~MyClass(){printf("destroy\n");} 
};
// reload operator new[]
void * operator new[](size_t size)
{
void *p = operator new(size);
printf("calling new[] with size=%d address=%p\n",size,p);
return p;
}
int main()
{
MyClass *ptrMC = new MyClass[2];
printf("address of ptrMC=%p\n",ptrMC);
delete[] ptrMC;
return 0;
}
MyClass的大小为4,申请2个类对象,需要8个字节就够,但实际上分配了12个字节,并且 operator new[]返回的地址 = 实际申请地址 + 4。
calling new[] with size=12 address=0x93af008
construct
construct
address of ptrMC=0x93af00c
destroy
destroy
使用gdb单步调试发现,实际申请地址为address=0x804a008,ptrMC=0x804a00c,0x804a008~0x804a00b这4字节对应的值为0x00000002。
也就是说这4个字节存放了我们分配的对象个数。
$ g++ newReload.cpp -g -o newReload
$ gdb newReload
... ...
(gdb) l
9       };
10      void * operator new[](size_t size)
11      {
12              void *p = operator new(size);
13              printf("calling new[] with size=%d address=%p\n",size,p);
14              return p;
15      }
16      int main()
17      {
18              MyClass *ptrMC = new MyClass[2];
(gdb) break 18
Breakpoint 1 at 0x80485e3: file newReload.cpp, line 18.
(gdb) info break
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x080485e3 in main() at newReload.cpp:18
(gdb) r
Starting program: /home/xiaoloaw/xiaoloaw_study/program/newReload
Breakpoint 1, main () at newReload.cpp:18
18              MyClass *ptrMC = new MyClass[2];
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.7.el6.i686 libgcc-4.4.4-13.el6.i686 libstdc++-4.4.4-13.el6.i686
(gdb) s
operator new[] (size=12) at newReload.cpp:12
12              void *p = operator new(size);
(gdb) s
13              printf("calling new[] with size=%d address=%p\n",size,p);
(gdb) s
calling new[] with size=12 address=0x804a008
14              return p;
(gdb) p p
$2 = (void *) 0x804a008
(gdb) x/x 0x804a008
0x804a008:      0x00000000
(gdb) s
15      }
(gdb) x/x 0x804a008
0x804a008:      0x00000000
(gdb) s
MyClass::MyClass (this=0x804a00c) at newReload.cpp:7
7               MyClass(){printf("construct\n");}
(gdb) x/x 0x804a008
0x804a008:      0x00000002
(gdb) s
construct
MyClass::MyClass (this=0x804a010) at newReload.cpp:7
7               MyClass(){printf("construct\n");}
(gdb) s
construct
main () at newReload.cpp:19
19              printf("address of ptrMC=%p\n",ptrMC);
(gdb) p ptrMC
$3 = (MyClass *) 0x804a00c
(gdb) x/x 0x804a00c
0x804a00c:      0x00000000
(gdb) x/x 0x804a00b
0x804a00b:      0x00000000
(gdb) x/x 0x804a00a
0x804a00a:      0x00000000
(gdb) x/x 0x804a009
0x804a009:      0x00000000
(gdb) x/x 0x804a008
0x804a008:      0x00000002
(2)类中没有显示声明析构函数
注释掉MyClass中的析构函数,运行结果如下。实际分配的大小为8字节,并且operator new[]返回的地址 = 实际申请地址
calling new[] with size=8 address=0x9f02008
construct
construct
address of ptrMC=0x9f02008
由此可知,是否在前面增加4个字节,取决于这个类有没有析构函数,确切说是这个类是否需要调用析构函数。
"需要调用析构函数的类":显式的声明了析构函数;拥有需要调用析构函数的类成员;继承了需要调用析构函数的类。
类似的,动态申请简单类型的数组时,也不会多申请4个字节。在这两种情况下,释放内存时使用delete或delete[]都可以,但是最好使用delete[]。
(3)释放内存时如何知道长度的?[malloc和free的机制]
既然申请无需调用析构函数的类或简单类型的数组时,没有记录个数信息,那么operator delete 或者直接说free()是如何回收这块内存的呢?
#include <stdio.h>
int main()
{
char *ptr = 0;
for (int i = 0; i< 40; i += 4)
{
char *sptr = new char[i];
printf("address = %p, alloc %2d bytes, distance = %d\n",sptr, i, sptr-ptr);
/*
for (int j = 1; j <= 4; j++)
printf("%p = %d ",sptr-j, *(sptr-j));
printf("\n");
printf("\n");
*/
ptr = sptr;
}
return 0;
}
分别申请大小为char[0]、char[4]、char[8]、... 、char[36]的内存,每一次alloc的字节数都比上一次多4,distance代表着与上一次分配的差值。
最小的差值为16,直到alloc 16字节时,差值变为了24;当alloc 24字节时,差值变为了32;...
继续分析,"实际分配的内存"与申请的内存的差值分别为(16-0)、(16-4)、(16-8)、(16-12)、(24-16)、...,差值最小的为4,也就是说实际分配内存比申请内存最少多分配了4个字节。
address = 0x908c008, alloc  0 bytes, distance = 151568392
address = 0x908c018, alloc  4 bytes, distance = 16
address = 0x908c028, alloc  8 bytes, distance = 16
address = 0x908c038, alloc 12 bytes, distance = 16
address = 0x908c048, alloc 16 bytes, distance = 16
address = 0x908c060, alloc 20 bytes, distance = 24
address = 0x908c078, alloc 24 bytes, distance = 24
address = 0x908c098, alloc 28 bytes, distance = 32
address = 0x908c0b8, alloc 32 bytes, distance = 32
address = 0x908c0e0, alloc 36 bytes, distance = 40
把每次分配内存得到的末尾4位打印出来(也就是下次申请内存首地址的前面4位),结果如下。从中可以看出多出的4位内存地址中保存了长度信息。
但是在执行free()时,是如何根据这些信息来删除的,需要后续继续研究。
address = 0x908c008, alloc  0 bytes, distance = 151568392
0x908c007 = 0 0x908c006 = 0 0x908c005 = 0 0x908c004 = 17
address = 0x908c018, alloc  4 bytes, distance = 16
0x908c017 = 0 0x908c016 = 0 0x908c015 = 0 0x908c014 = 17
address = 0x908c028, alloc  8 bytes, distance = 16
0x908c027 = 0 0x908c026 = 0 0x908c025 = 0 0x908c024 = 17
address = 0x908c038, alloc 12 bytes, distance = 16
0x908c037 = 0 0x908c036 = 0 0x908c035 = 0 0x908c034 = 17
address = 0x908c048, alloc 16 bytes, distance = 16
0x908c047 = 0 0x908c046 = 0 0x908c045 = 0 0x908c044 = 25
address = 0x908c060, alloc 20 bytes, distance = 24
0x908c05f = 0 0x908c05e = 0 0x908c05d = 0 0x908c05c = 25
address = 0x908c078, alloc 24 bytes, distance = 24
0x908c077 = 0 0x908c076 = 0 0x908c075 = 0 0x908c074 = 33
address = 0x908c098, alloc 28 bytes, distance = 32
0x908c097 = 0 0x908c096 = 0 0x908c095 = 0 0x908c094 = 33
address = 0x908c0b8, alloc 32 bytes, distance = 32
0x908c0b7 = 0 0x908c0b6 = 0 0x908c0b5 = 0 0x908c0b4 = 41
address = 0x908c0e0, alloc 36 bytes, distance = 40
0x908c0df = 0 0x908c0de = 0 0x908c0dd = 0 0x908c0dc = 41 
参考:
http://blog.csdn.net/songthin/article/details/1703966

C++ new的理解相关推荐

  1. 通用解题法——回溯算法(理解+练习)

    积累算法经验,积累解题方法--回溯算法,你必须要掌握的解题方法! 什么是回溯算法呢? 回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就&quo ...

  2. stream流对象的理解及使用

    我的理解:用stream流式处理数据,将数据用一个一个方法去 . (点,即调用) 得到新的数据结果,可以一步达成. 有多种方式生成 Stream Source: 从 Collection 和数组 Co ...

  3. Linux shell 学习笔记(11)— 理解输入和输出(标准输入、输出、错误以及临时重定向和永久重定向)

    1. 理解输入和输出 1.1 标准文件描述符 Linux 系统将每个对象当作文件处理.这包括输入和输出进程.Linux 用文件描述符(file descriptor)来标识每个文件对象.文件描述符是一 ...

  4. java局部变量全局变量,实例变量的理解

    java局部变量全局变量,实例变量的理解 局部变量 可以理解为写在方法中的变量. public class Variable {//类变量static String name = "小明&q ...

  5. 智能文档理解:通用文档预训练模型

    预训练模型到底是什么,它是如何被应用在产品里,未来又有哪些机会和挑战? 预训练模型把迁移学习很好地用起来了,让我们感到眼前一亮.这和小孩子读书一样,一开始语文.数学.化学都学,读书.网上游戏等,在脑子 ...

  6. 熵,交叉熵,散度理解较为清晰

    20210511 https://blog.csdn.net/qq_35455503/article/details/105714287 交叉熵和散度 自己给自己编码肯定是最小的 其他的编码都会比这个 ...

  7. mapreduce理解_大数据

    map:对不同的数据进行同种操作 reduce:按keys 把数据规约到一起 看这篇文章请出去跑两圈,然后泡一壶茶,边喝茶,边看,看完你就对hadoop 与MapReduce的整体有所了解了. [前言 ...

  8. 文件句柄和文件描述符的区别和理解指针

    句柄是Windows用来标识被应用程序所建立或使用的对象的唯一整数,Windows使用各种各样的句柄标识诸如应用程序实例,窗口,控制,位图,GDI对象等等.Windows句柄有点象C语言中的文件句柄. ...

  9. 通俗理解条件熵-数学

    就是决策树里面选划分属性用到的计算 条件熵越小表示划分之后各个集合越纯净 前面我们总结了信息熵的概念通俗理解信息熵 - 知乎专栏,这次我们来理解一下条件熵. 我们首先知道信息熵是考虑该随机变量的所有可 ...

  10. 通俗理解tf.nn.conv2d() tf.nn.conv3d( )参数的含义 pytorhc 卷积

    20210609 例如(3,3,(3,7,7))表示的是输入图像的通道数是3,输出图像的通道数是3,(3,7,7)表示过滤器每次处理3帧图像,卷积核的大小是3 x 7 x 7. https://blo ...

最新文章

  1. 反射机制(1)认识Class类
  2. Linux命令整理 —— 目录结构
  3. idea下载与安装 0913
  4. 如何编写好的C main函数
  5. Python 脚本如何执行另一个脚本
  6. OpenSea联合创始人Devin Finzer:NFT空间在未来几年会产生更多应用 | FBEC 2020
  7. python ioc框架_轻松理解 Spring 中的 IOC
  8. TODO C++ 异常处理
  9. 索尼工厂被迫停止生产,日本地震带来的冲击可能不止于此
  10. 男人想要成功--必须明白的22个道理
  11. IOS fiddler抓包配置
  12. SQL数据库置疑数据怎么恢复
  13. Hello Python(十七)——Python扩展模块开发
  14. Unity - Timeline 之 Deleting tracks(删除轨道)
  15. 前端+后端项目 - 论坛信息管理系统(Web+servlet+MySQL+JDBC)
  16. 公积金单位账号和个人账号
  17. 家用计算机如何连无线网,电脑上怎么连接wifi_怎样连接自己家的wifi-win7之家
  18. 2023年电工杯数学建模竞赛A题:电采暖负荷参与电力系统功率调节的技术经济分析具体建模过程以及代码结果
  19. 转战物联网·基础篇01-物联网之我见
  20. 【机器学习】拟合优度度量和梯度下降(红酒数据集的线性回归模型sklearnRidge)

热门文章

  1. 培训经历和培训现状,供开发人员参考
  2. 手机远程控制泵站,自动智能管理空气源热泵
  3. 【网页前端】CSS常用布局之定位
  4. 以太坊的数据存储结构
  5. 现在做电商,该怎么获取商品的图文详情呢?(关键词搜索商品详情、销量、图片)
  6. 不影响播放的前提下,如何加密车载U盘?
  7. SPSS教程:信度分析之克朗巴哈系数(Cronbach‘s α)
  8. [转载] 全国各省市免费发送代码到10086查询GPRS流量指令大全
  9. 高效率同步4开关Buck-Boost DC/DC控制器TMI5700
  10. Laravel框架配置日志按天生成在文件中