问题:
在网上看人写了这么一段代码:
1 class A 2 { 3 public: 4 A() 5 { 6 std::cout<<"call A constructor"<<std::endl; 7 } 8 9 ~A() 10 { 11 std::cout<<"call A destructor"<<std::endl; 12 } 13 14 void* operator new(size_t size) 15 { 16 std::cout<<"call A::operator new[] size:"<<size<<std::endl; 17 return malloc(size); 18 } 19 void operator delete[](void* p) 20 { 21 std::cout<<"call A::operator delete[]"<<std::endl; 22 free(p); 23 } 24 void operator delete(void* p) 25 { 26 free(p); 27 } 28 };#include <iostream> #include "A.h" 29 30 void* operator new[](size_t size) 31 { 32 std::cout<<"call global new[] size: "<<size<<std::endl; 33 return malloc(size); 34 } 35 36 void operator delete[](void* p) 37 { 38 std::cout<<"call global delete[] "<<std::endl; 39 } 40 int _tmain(int argc, _TCHAR* argv[]) 41 { 42 std::cout<<"sizeof A "<<sizeof(A)<<std::endl; 43 A* p1 = new A[3]; 44 delete []p1; 45 46 system("pause"); 47 return 0; 48 }
如果定义了析构函数的话:
operator new[]会输出:
operator new[]会输出:
call global new[] size:7
否则输出:
call global new[] size:3
解答:
这里提到了delete并不知道数组长度是多少。这个职责由new[]完成。标准库中new[]的实现一般是先申请一块sizeof(T) * n + x的空间,使用最初的空间记录数组长度,从下一个对齐了的地址开始才是对象数组实际使用的空间。这点可以以下通过简单修改程序观测到。构造函数中打印this指针,new[]函数中先记录malloc的值,打印并返回。
多出来的这个x空间具体是多少呢,这个C++标准并没有严格定义,只是给出一些条件。
多出来的这个x空间具体是多少呢,这个C++标准并没有严格定义,只是给出一些条件。
大意是最终应该保证对象实际所占用的空间必须符合当前平台的内存对齐要求。对于x86来说是4字节对齐,即指针作为一个整数必须能被4整除。对于x64来说是8字节对齐。结合上面那段new[]应该负责记录数组长度这一规定,可以得出如下规则。
1. 由new[]返回的空间中最初空间用于长度记录,答主实测VC上使用的是4字节int。
2. 依据内存对齐要求补齐空间。
3. 对象数组实际占用的空间。
整体情况就是这样: [size][pad][array]
如何检测我们的结论?
在主函数中new和delete之间加入如下代码:
1. 由new[]返回的空间中最初空间用于长度记录,答主实测VC上使用的是4字节int。
2. 依据内存对齐要求补齐空间。
3. 对象数组实际占用的空间。
整体情况就是这样: [size][pad][array]
如何检测我们的结论?
在主函数中new和delete之间加入如下代码:
1 size_t mask = sizeof(void*) - 1; 2 size_t p2 = reinterpret_cast<size_t>(p1 - 1); 3 p2 = p2 & ~mask; 4 std::cout << *reinterpret_cast<int*>(p2) << std::endl;
就可以得到数组的大小。
2. 没有析构函数时候的情况
在1中我们提到new[]申请的空间可以大于数组实际所需空间,以记录数组长度,但在缺少有效析构函数(non-trivial destructor,非标准中译)的情况下delete仅仅将这一块连续的内存空间释放就可以了,所以无需记录数组长度,这时new[]申请的空间可以等于数组实际所需的空间。
关于有效析构函数
在1中我们提到new[]申请的空间可以大于数组实际所需空间,以记录数组长度,但在缺少有效析构函数(non-trivial destructor,非标准中译)的情况下delete仅仅将这一块连续的内存空间释放就可以了,所以无需记录数组长度,这时new[]申请的空间可以等于数组实际所需的空间。
关于有效析构函数
简单说就是自身及其非静态成员(包括继承的)都必须没有定义或删除了析构函数。申请这样对象的数组时一般不会使用额外空间去记录其长度。