new与operator new的区别?
可以把new理解为C++中的一个关键字(准确地说是操作符)。
operator new可以当成一个函数名,可能有人会问,函数名怎么可以有空格呢?其实这无伤大雅,想想我们在重载运算符时(+,-等 运算符),函数名的位置不也是类似operator+, operator-吗,+/-符号在重载时也可以作为函数名的一部分。operator new想相当于operator+, operator-的位置。至于到底为什么这里函数名可以有空格,-,+,那是编译器的细节,不多作讨论。
只需要把operator new当成一个函数名就好了。
new的行为分析
这里指的new是指程序中形如: A *a = new A;
中的new
关于想要查看new操作符的执行过程,可以查看汇编代码来简要分析,当然也可以直接查看编译器的源码,看是怎么处理的。
下面我们来看一下当使用new创建对象时的过程。
测试代码:
对应的汇编代码:使用objdump -d a.out
查看
一些参数处理不用关注,只看主要的几条,也就是与代码中new Demo直接相关的:
a6a: e8 81 fe ff ff callq 8f0 <_Znwm@plt>
a75: e8 8e 00 00 00 callq b08 <_ZN4DemoC1Ev>
这两句是调用了两个函数,这两个函数的名字(_Znwm, _ZN4DemoC1Ev)是经过name mangling的,可通过如下程序进行转换:
#include <iostream>
#include <cxxabi.h>
using std::cout;
using std::endl;
int main(int argc, char *argv[])
{
int s;
cout << argv[0] << " --> ";
cout << abi::__cxa_demangle(argv[1], NULL, NULL, &s) << endl;
return 0;
}
上面代码很简单,编译后直接使用,就可查看经过name mangling的标识符对应的原名称。
查看new调用的两个函数:
可以看到,new操作符其实是调用了两个函数:
- operator new(unsigned int) ------ 分配内存空间
- Demo::Demo() -------- 调用构造函数在上面申请的空间上构建对象
那么new操作符的描述可以如下(不能运行):
new(Type t)
{
void *buf = operator new(sizoef(Type)); /* 调用operator new来申请内存 */
char *tmp = (char*)buf;
Type *ret = new(tmp) Type; /* 这句话相当于(Type*(tmp))->Type::Type(), 就是调用构造函数在指定内存构造对象,如果前边的不好理解,就用注释里的理解,但是g++好像不支持直接调用构造函数 */
return ret;
}
下面是operator new的源码:
operator new这个函数(全局函数),其实就是包装了一下malloc(处理异常), 这也就是为什么说它是申请内存的了。
执行这儿,只申请了内存,并没有构造对象,所以接下来的工作就是通过一定的方式在指定的内存(申请的 )构造对象。
当我们在程序中执行new A
创建对象时,
编译器会把new
操作符拆分成上面的两个操作,然后生成相应的汇编指令。
也就是说operator new是new操作符的子操作。
注:在执行new A
时不一定完全生成两个操作,编译器可能会进行优化。比如:使用默认构造函数时,就没有调用构造函数的步骤,只使用operator new分配的内存。
重载new操作符
虽然说是重载new操作符,但是实际我们只能改变operator new分配空间这一个步骤,至于第二个步骤我们是不能干预的,由编译器来决定。
例子:
输出结果:
我们确实改变了operator new(也就是new的第一个步骤)。对应大小为8个字节(编译器计算)
但是上面的例子不能使用delete a; 因为我们在operator new这个步骤没有分配空间。
为什么说可以认为operator new是一个函数呢?
结果:
可能看到36, 37, 38行是可以把operator new当函数来调用的。(只不过函数名比较特殊罢了)
使用new
创建对象时,如果没有重载operator new, 那么就调用全局的operator new(上图中可以看到,重载的operator new注释了后,调用全局函数operator new(最上方)),如果我们自己没写,也没重载,那么就调用已经定义好的operator new来分配空间。
这个代码在gcc的源码中可以找到。
同理:
delete也是两个步骤:
- 先销毁对象, Type o; o->~Type(); /* 调用析构函数 */
- 释放内存:free(o);
总结:
- new, delete都是分两个步骤进行的,分配空间/处理对象。
- operator new与new的区别,operator new是new的一个子操作,可以当成函数
- 我们重载new时,只能重写operator new, 而不能干预构造函数的调用(由编译器处理)
本文对一些概念的理解和解释可能不是很准确。
如果有错误,恳请读者批评指正。