变长数组(非const 变量来定义数组的长度)是每个C++开发人员梦寐以求的东西。通常实现 C++变长数组时,主要通过 new(或 malloc)实现。如下面这段代码:
nt inamelen=100:
char *pNameStr=new char [inamelen ];
但这种实现有两个显著的缺点:
(1) pNameStr指针无法记录自己的长度,它的长度必须另行存储,而且还需明确知道记录 pNameStr 长度的变量是 inamelen。假如记错了,那么你的程序就会存在崩溃的风险。
(2) pNameStr 在使用完后必须还给内存,否则就会存在内存泄漏的风险,因为pNameStr存储区在堆上。
我们继续看这样的结构体,它可以记录数组的长度。首先看一下这样的实现形式:
struct NameStr
{
Int namelen; //名称字符串长度
char*psznamestr; //名称字符串指针
}
//创建一个可变数组,数组长度为n
Struct NameStr MyName;
MyName.namelen =n;
MyName.psznamestr= malloc(n*sizeof(char ))
这种结构体的数组大小是动态分配的,但这样分配的地址是不连续的(即psznamestr 指向的数组不是接在 MyName.namelen 的后面),这样有时候会不方便,比如说用 memcpy 去从 MyName 首地址开始复制,只能复制 namelen和 psznamestr的值(即数组的首地址),如果想要得到数组里的数据,还要用memcpy 从 psznamestr 的值再来复制一次,这样就很麻烦,特别是在 socket 编程的时候,每次传出的数据都是“大小+数据”的模式,如果是这样写的结构体,就需要调用两次传输函数。但是下面这种写法可以解决这个麻烦。
在 C89 中,有一种称为 struct hack 的方法来得到可变数组。我们首先看下面这个可变数组实现:
struct NameStr
{
intnamelen; // 名称字符串长度
char namestr[1]; // 名称字符串数组地址
}
//创建一个可变数组,数组长度为n
struct NameStr*p NameStr=malloc(sizeof(struct NameStr) + (n-1)*sizeof(char ));
动态数组Namestr 分配于堆上,在堆内存上的布局如图2-4所示。创建时通过申请内存生成,使用完毕后必须释放申请的内存。虽然这种实现同样必须申请和释放内存,但是它有一个明确的优点,即这种实现可以记录动态数组的长度。
小心struct hack陷阱
● struct hack 动态数组必须分配于堆上。它通过 struct 数据结构实现,动态数组成员(如 namestr)必须是 struct 的最后一个数据成员。
● struct hack 可记录动态数组的长度。struct hack 同样存在一个缺点,即不需要编程人员手工分配和释放。
struct hack方式虽然解决了动态数组的问题,但是 struct hack 得到的数组长度n让人感觉有些蹊跷(在内存申请时只申请了n-1个,而实际上分配的数组长度却为 n)。所以为了解决这种问题,C99提供了类似但合法的 struct 机制。采用这种机制,上述可变数组实现可改写成下述代码:
struct NameStr
{
int namelen; //名称字符串长度
char namestr []; //名称字符串数组地址
}
//创建一个可变数组,数组长度为n
struct NameStr*p NameStr=malloc(sizeof(struct NameSt) + (n)*sizeof(char ));
C99的这种 struct 机制让 struct hack 动态数组实现更符合编程人员的视觉需要,也更容易理解。但它依然没有解决编程人员手动内存分配而引入的内存泄漏风险。
最后,我们看一种真正的动态数组实现方式,这种实现方式一可解决编程人员手动内存操作的内存泄漏风险,二可具有普通数组的一切特性,但是这种数组的长度可以是一个非 const 变量。这就是 C99 新引入的变长数组功能。
新的C99标准新增了变长数组的支持,用户可以像以前定义数组一样定义一个变长数组。数组长度可以是一个非 const 变量。可变数组的空间大小直到程序运行时才能确定,因此只有程序在运行时才能为数组分配空间。GCC编译器会在程序运行时根据实际指定的大小(变量当前的值)调节ESP的值,为数组在栈上分配适当大小的空间。由于在运行时才能为数组分配空间,在开始分配空间之前空间的大小是不确定的,因此分配空间的起始地址也是不确定的(如要在栈上分配两个可变长数组的情况下)。
为了在以后的代码中对可变长数组的内容进行引用操作,程序必须通过某种方式获取可变长数组的地址。在 GCC 编译器中,会在相对于 EBP 固定偏移量的栈上分配的一个固定大小的区域(称为内情向量)来记录可变长数组的信息,如数组的开始地址等,后续代码通过内情向量中的起始地址访问可变长数组。因为数组依靠在程序运行时动态地调整EBP来分配空间,所以这种类型的数组只能够定义在栈内,不能够定义在数据段(全局数组、静态数组)上。
下面这段代码是采用动态数组的编程实现:
#include<stdio.h>
#include<stdlib.h>
int main()
{
unsigned int uiArrySize =0; //变长数组非const常量长度存储单元
fscanf(stdin, "%d",&uiArraySize);
int aiArray[uiArySize]; //定义变长数组
if(0 ==uiArry Size) //判断长度是否为0。若非0,则打印数组数据
{
printf("aiArray array is a empty array!\n");
returm 0;
}
Else
{
/*错误:loop初始化声明仅仅适用于C99模式下
Note:需应用-std=c99 或-std-gnu99 编译选项*/
for(inti=0;i<uiArrySize;i++)
{
printf("%d\t", aiArra [i]);
}
}
}
小心陷阱
● 虽然大部分编译器已支持C99标准,但是至今依然有很多编译器不支持可变数组,它们遵从的依然是C89标准。
● 可变数组必须分配到栈上,不能分配到数据段上,如全局数组、静态数组就不能定义为可变数组。
请谨记
● C99支持的可变数组功能的确可以给编程带来很多方便,但请谨慎使用,因为并不是所有的编译器都支持这种新功能。
● C89支持 struct hack可变数组实现,虽然要求编程人员自己去申请和释放数组,但是在要求跨平台和兼容性较好的编程中,这种实现未必不是一个最佳的选择。