variable length arrays

2007-07-03 13:37

备注:测试发现这种数组只能在函数内,不能是全部变量数组,否则编译不过!

    昨天老钱傻乎乎的问了个变长数组的问题,弄得大家莫名惊诧。老钱这孩子就是喜欢弄一些幼稚无比又莫名其妙的东东来折磨大家。bs之。
    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++编译器尚不能支持动态数组特性(VC++2005不支持此特性, 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.变长数组不能在静态存储区中, 这个很显然, 静态存储区在编译时就确定分配内存的大小,不像堆栈一样是动态的, 变长数组显然不能定义在静态存储区中. 

    5. 变长数组的意义, 我觉得在c里面还是有一些意义, 如果是在对性能,内存要求十分严格的地方, 允许声明变长数组还是比较方便的, 因为原来需要一块连续内存的地方要么声明的大一些,保证肯定够用, 但这样就浪费了, 在对内存要求严格的地方就不好了, 另外一种做法就是malloc动态分配, 但是这样的缺点是需要手动管理, 要手动free, 程序如果大的话内存管理不好的话容易内存泄漏,所以在c里面还是有一些意义. 但是在c++里面已经有vector了, 而且c++也不太多应用在对性能,内存要求非常高的地方,  
   
    6.使用变长数组, 最重要的当然是要检查数组的size的合法性了, 我前面举例为了减少干扰因素, 都没有做检查, 实际的程序肯定要做这个检查, 则面试的时候这样的程序肯定立刻被kick. 另外, 就是又多了一种堆栈溢出的方法,数组大小如果是负的话又可以跳到不该跳到的地方了 


    [1]  Incompatibilities Between ISO C and ISO C++
    [2] 
变长数组实现初探    http://edu.stuccess.com/knowcenter/CProgramming/language/array/00000000.htm  
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值