1. 简介

在面向对象编程中,我们经常会用到这样的动态分配数组:

1
Person* aPerson1 = new Person[100];

在上述申请数组的过程中,我们使用到了new []这个表达式来完成,它会调用类的构造函数初始化这个数组。
如果我们用完了这个数组,想要释放空间,就需要调用:
1
delete[] aPerson1;

在这个过程中,我们使用了delete[]来完成释放空间。
但是两个问题出现了:

  1. 如何知道aPerson1数组的内存空间大小?
  2. 如何知道要调用几次析构函数?

显然,想要知道数组有多长,我们必然要存下这个数组的长度。c++中也正是这么做的。如何实现的呢?请看下文。

2. 结合代码的分析

注:本文适用于64位mingw。在32位mingw下后文内8字节应为4字节,long long应为int。

完整代码见文尾

我们先定义一个自定义的类,重载其new[], delete[]两个操作符。

1
2
3
4
5
6
7
8
class Yu {
public:
int iNumber;//Yu类内将只有这一个int变量
//即 一个Yu对象的大小=一个int的大小=4字节
Yu(){iNumber = 1;}
~Yu(){}
...
};

同时,在main中声明一个长度为len(20194134)的Yu类型的数组,再获取该数组开头的地址并打印。

1
2
3
4
5
6
7
8
const long long len = 20194134;
int main(){
...
Yu* testArr = new Yu[len];
char* p1 = (char*) testArr;
cout << "Address for the Array: "<< testArr <<endl;
...
}

输出为:Address for the Array: 0x2670048

接着我们重载new[]操作符。

1
2
3
4
5
void* operator new[](size_t sz){
printf("|Length: %lld\n|Real Size: %lld\n|Raw Size(int(4)*length): %lld\n|Gap: %lld\n",len,sz,len*4,(long long)sz-len*4);
void* o = malloc(sz);
return o;
}

在这个过程中,我们打印四个关键数值:

  1. 数组的长度
  2. new[]过程中申请的内存空间大小
  3. 数组理论上需要的内存空间
  4. 2、3的差值

其中,因为Yu类型只有iNumber一个内部变量,所以
一个Yu对象的大小=一个int的大小=4字节
3的计算:数组长度×sizeof(int)

代码中len的值为:20194134

得到输出:

1
2
3
4
|Length: 20194134
|Real Size: 80776544
|Raw Size(int(4)*length): 80776536
|Gap: 8

这里会发现,在实际需要的内存空间与理论空间中,存在8个字节的差值。
而8个字节,恰好是一个long long变量的大小。我们不妨猜测,这8个字节正是储存着数组的长度。
那,我们可以大胆猜想,析构开始的地址也会恰好是数组储存的地址8位之前。(即 析构地址+8=数组地址)
接下来我们尝试验证这个猜想。
重载delete[]操作符,并在主函数中调用:
1
2
3
4
void operator delete[](void *o){
cout<<"Destruct from: "<<o<<endl;
free(o);
}

1
2
3
4
5
6
7
8
int main(){
...
char* p1 = (char*) testArr;
cout << "Address for the Array: "<< testArr <<endl;
...
delete[] testArr;
return 0;
}

最后几行的输出为:
1
2
3
Address for the Array: 0x2670048
...
Destruct from: 0x2670040

成了!我们发现,解构时得到的地址(0x2670040)恰好是数组的地址(0x2670048)减八。
即:0x2670048 = 0x2670040 + 8
接下来,我们更进一步,取出数组地址-8对应地址的一个long long变量,看一下它的值会是什么.

接下来获取该数组前的8字节,识别为long long并打印。

1
2
3
4
5
6
7
8
int main(){
...
char* p1 = (char*) testArr;
cout << "Address for the Array: "<< testArr <<endl;
cout<< "The long long before the Array: " << *(long long*)(p1-len_ll)<<endl;
delete[] testArr;
...
}

输出为:
1
2
3
Address for the Array: 0x2670048
The long long before the Array: 20194134
Destuct from: 0x2670040

代码给出的结果证明了前述的猜想:
自定义类数组前的8个字节,是一个long long类型的变量,储存了该数组的长度

3. 用于实践的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>
#include <string>
using namespace std;

const long long len = 20194134;

class Yu {
public:
int iNumber;
Yu(){iNumber = 1;}
~Yu(){}
//重载 new[]操作符
void* operator new[](size_t sz){
/*打印四个关键数据:
①数组的长度
②new[]过程中申请的内存空间大小
③数组实际需要的内存空间
④ ②、③的差值
*/
printf("|Length: %lld\n|Real Size: %lld\n|Raw Size(int(4)*length): %lld\n|Gap: %lld\n",len,sz,len*4,(long long)sz-len*4);
//完成内存分配
void* o = malloc(sz);
return o;
}
//重载delete[]操作符
void operator delete[](void *o){
//打印析构的开端地址
cout<<"Destruct from: "<<o<<endl;
//完成内存释放
free(o);
}
};

int main(){
// 打印long long的大小
int len_ll = sizeof(long long);
cout<<"Size of long long: "<<len_ll<<endl;
//生成一个长度为len(20194134)的Yu类型的数组
Yu* testArr = new Yu[len];
//获取该数组开头的地址并打印
char* p1 = (char*) testArr;
cout << "Address for the Array: "<< testArr <<endl;
//获取该数组前的8字节,识别为long long 并打印
cout<< "The long long before the Array: " << *(long long*)(p1-len_ll)<<endl;
//释放数组空间
delete[] testArr;
return 0;
}

完整输出:

1
2
3
4
5
6
7
8
Size of long long: 8
|Length: 20194134
|Real Size: 80776544
|Raw Size(int(4)*length): 80776536
|Gap: 8
Address for the Array: 0x2670048
The long long before the Array: 20194134
Destuct from: 0x2670040