C++ new的理解
一、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