该文章转自:上次说了静态数组可变长,今天知道原理了
Ⅰ. 变长数组是长度一直可以变的吗
变长数组,那么是长度一直可以变的吗,到底什么时候这个长度会确定下来呢?
我们先看一下代码,如下:(注意vs编译器中不支持变长数组)
#include <iostream>
using namespace std;
int main()
{
int size = 1000;
cout << "please input a number:";
cin >> size;
int arr[size];
cout << "please input a number too:";
cin >> size;
cout << "arr's size is " << sizeof(arr)/sizeof(arr[0]) << endl;
return 0;
} 假设我们第一次输入100,第二次输入10000,那么最后一个cout到底输出多少呢,答案是100。
这里的所谓变长数组,实际上指的是可以使用变量来作为数组的元素个数,在还未运行到声明数组的地方时,还可以通过改变变量的值来修改数组的元素个数,但是等到运行到数组声明的地方后,这个数组的大小就确定了,后续就不能再改变了,所以所谓的变长也只是相对于运行到这个数组声明的地方而言。
Ⅱ. 变长数组是分配在堆上吗
当然不是,注意这里概念不要搞混淆了,变长数组不是动态数组,虽然是到运行时才确定大小,但说到底它还是局部变量,而局部变量,又没有动态申请内存的动作,当然也只会在栈上分配内存。
关于这一点我们可以结合gdb和寄存器地址来看一下,我的机器是x86架构,那么栈的内存分配方式就应该是从高地址往低地址分配,如果学过汇编的会知道,gcc 编译的时候不添加 -O1 或者 -O2 这样的选项,那么栈底指针地址会保存在寄存器 rbp 中,而栈顶指针则会保存在 rsp 寄存器中,如果地址是在 rsp 和rbp 之间的,那就可以肯定这段内存是保存在栈中了。
还是这段代码:
//test.cpp
#include <iostream>
using namespace std;
int main()
{
int size = 1000;
cout << "please input a number:";
cin >> size;
int arr[size];
return 0;
} 使用 g++ -g test.cpp(注意这里千万不能加优化选项)编译后,gdb ./a.out 看一下:
(gdb) b main #打断点
Breakpoint 1 at 0x4007fd: file test.cpp, line 11.
(gdb) r
Starting program: /root/a.out
Breakpoint 1, main () at test.cpp:11
11 return 0;
(gdb) n
6 int size = 1000;
(gdb)
7 cout << "please input a number:";
(gdb)
8 cin >> size;
(gdb)
please input a number:10000
9 int arr[size];
(gdb) p $rbp #打出基址寄存器的值
$1 = (void *) 0x7fffffffe840
(gdb) n
11 return 0;
(gdb) p &arr[0]
$3 = (int *) 0x7fffffff4ba0
(gdb) p $rsp #打出栈顶指针的值
$4 = (void *) 0x7fffffff4ba0
(gdb) 可以看到,arr 数组的首地址是在 (rsp, rbp) 这个范围之间的,所以运行时的变长数组也是保存在栈中的,既然是在栈中,那么这段内存在这个变量作用域结束以后就会被释放掉。
Ⅲ. 变长数组与alloca函数
同时这次也是涨知识了,我才知道 c 标准库中有个 alloca 函数,它的用法类似 malloc,但内存因为是在栈上申请的,也是不需要手动释放的,这么一看,这个函数的原理和变长数组其实是一样的,都可以使用运行时才确定大小的变量值来申请栈空间,并且不需要手动释放。
知道以后,我也很好奇,所以还专门去研究了下这两者的汇编指令是不是一样,先看下 c++ 代码:
#include <iostream>
#include <alloca.h>
using namespace std;
int main()
{
int size = 1000;
cout << "please input a number:";
cin >> size;
int arr[size];
int *p = (int*)alloca(size);
return 0;
} 然后通过 gdb,我分别打印出来 int arr[size] 和 int *p = (int*)alloca(size) 这两行代码所对应的汇编指令,做了一个对比,如图:

可以看得出来变长数组是在一开始先做了一些其他的动作,然后后面的指令跟alloca的指令基本就是一样的了,也就是说最终他两的实现是比较类似的。
Ⅳ. 变长数组使用注意点
基于变长数组的特点,它其实相当于一个变相版的动态申请内存,只是不需要堆而言,而这种场景多应用于小型机里面,比如很多嵌入式环境,因为资源有限,是没有堆内存的,那如果又需要动态改变数组大小怎么办,就可以使用变长数组,但这时也需要限定一下大小,不然很容易就会造成栈溢出。