C中的Variable length arrays (变长数组)

Variable length arrays 是C99的特性,而不是 C++98 的,关于c99标准的变长数组, 在标准的6.7.5.2 Array declarators里面有这样的说明: 2.Only ordinary identifiers (as defined in 6.2.3) with both block scope or function prototype scope and no linkage shall have a variably modified type. If an identifier is declared to be an object with static storage duration, it shall not have a variable length array type. 换而言之, 变长数组不能是在静态存储区(包括全局变量和静态变量)中的。 另外,VLA 需要支持 sizeof 运算, 动态sizeof 也是C99的一个特有特性。 目前很多C+编译器尚不能支持动态数组特性(VC2005不支持此特性, GCC3.2之后支持,之前的版本没有调查,不知道是从哪个版本开始支持此特性的)。gcc的文档里面说:Variable-length automatic arrays are allowed in ISO C99, and as an extension GCC accepts them in C89 mode and in C+。所以,使用GCC,即使你打开 -ansi (等价于 –std=c89)选项,也仍然可以使用动态数组。 其实C99的VLA也不是真正意义上的变长数组—它只是在运行时可以根据一个变量生成一个数组,此数组此后并不会变化,而真正意义的变长数组可以在实际使用时可以动态伸缩。 过去的C 和现在的C++中,自动变量的偏移都是预定的,但是VLA不能这样实现。 下面的例子中,由于b, c大小不确定,所以它们的地址必须是运行时确定的

void test(int tmp)
{
	int a;
	scanf("%d", &a);
	int b[tmp];
	int c[a];
	printf ("size(b) = %d, size(c) = %d\n", sizeof(b)/sizeof(int), sizeof(c)/sizeof(int));
}

VLA在栈上分配空间,这个速度很快。 我们举一个典型的32位编译器下函数实现为例子。这个实现中,下面两个寄存器关注函数堆栈:

ebp: 函数占有内容起点即帧(frame)的位置, esp:当前栈顶

一般来说,一个函数中自动变量个数和大小都是确定的。比如共有4个int,一个大小为10的 float 数组。 编译器可以简单的 esp -= 56 就留出所有空间(以32位PC机为例)。 每个变量相对 ebp 的偏移也是确定的,所以,若他访问 int a,

$a = 4, 他会这样访问: $a [ ebp ]

对于 VLA 来说,事情复杂一些。 现在有一个 VLA c。 他的大小是不确定的 —— 所以它的偏移也是不确定的:如果它之前有另外一个 VLA b, 它不可能知道自己排到哪里。目前 GCC 中的做法是,在可变数组较少的情况下,直接用寄存器+偏移的方式来存取;在较多的情况下,都是先从堆栈中得到数组的起始地址, 然后再用偏移量的方式存取数组的元素。

这个操作是非常快的。不过有一个问题是,栈的大小是固定的,而且不大。你不能分配很大的东西。另外这个数组是临时的 —— 一旦退出当前作用域,他就可以被任何其他函数刷掉。

变长数组的特性综述(摘自[2]):

  1. 变长数组是分配在堆栈上的, 其实从语义的角度也应该是这样, 变长数组还是一个数组, 还是一个局部变量, 在c语言中, 局部变量是分配在堆栈上的, malloc才是分配在堆上面的. 这里面没有不存在什么内存泄漏, 因为堆栈上的内存是不需要程序员管理的

  2. 变长数组和其他变量共同存在于一个作用域之内时, 变长数组是在最后分配的, 也就是处在堆栈的最下面(地址最低). 这也是变长数组不同于普通变量的情况, 普通变量的内存分配是按照定义顺序来的, 先定义的先分配, 但是变长数组不是这样的, 在一个作用域内变长数组都是放到最后分配的, 原因很简单, 变长数组的大小在编译时无 法确定, 所以如果在变长数组后面分配普通变量的空间, 那么对后面普通变量的存取就不方便了,当然简单的情况可以通过优化解决, 但是如果有很多的变量数组和普通变量混合在一起的话,优化很难做到很好, 所以把变长数组都放在最后面分配, 是一个比较合理的办法; 当然, 不是说一定要放在最后, 按照普通的定义顺序来分配空间也是可以的,只是在存取可变数组后面的变量的时候, 需要首先找到可变数组的起始地址, 然后再往下(低地址) 找到后面变量的地址, 存取相应的变量。目前gcc所使用的放到最后的做法, 也可以理解成一种优化. 。

  3. 对变长数组的元素存取, 可以把首地址放在寄存器里面, 也可以在把首地址放在堆栈里面,然后存取元素的时候先取出首地址, 然后在通过偏移量的方式存取里面的元素.

4.变长数组不能在静态存储区中, 这个很显然, 静态存储区在编译时就确定分配内存的大小,不像堆栈一样是动态的, 变长数组显然不能定义在静态存储区中.

  1. 变长数组的意义, 我觉得在c里面还是有一些意义, 如果是在对性能,内存要求十分严格的地方, 允许声明变长数组还是比较方便的, 因为原来需要一块连续内存的地方要么声明的大一些,保证肯定够用, 但这样就浪费了, 在对内存要求严格的地方就不好了, 另外一种做法就是malloc动态分配, 但是这样的缺点是需要手动管理, 要手动free, 程序如果大的话内存管理不好的话容易内存泄漏,所以在c里面还是有一些意义. 但是在c+里面已经有vector了, 而且c+也不太多应用在对性能,内存要求非常高的地方,

6.使用变长数组, 最重要的当然是要检查数组的size的合法性了, 我前面举例为了减少干扰因素, 都没有做检查, 实际的程序肯定要做这个检查, 则面试的时候这样的程序肯定立刻被kick. 另外, 就是又多了一种堆栈溢出的方法,数组大小如果是负的话又可以跳到不该跳到的地方了

转自:http://hi.baidu.com/fenqin/blog/item/e86a5c602c3364dd8db10d9c.html


--EOF--

Published

10 February 2010

Tags