C99标准引入了变长数组(Variable Length Arrays, VLA)的概念,允许数组在运行时根据用户输入或其他条件动态地确定大小,下面是一个变长数组的使用例子:
cpp
void f(int n)
{
char a[n];
a[n-1] = 'a';
a[0] = 'b';
}
上面的例子可以看出,使用变长数组时可以在运行时确定数组长度而不是c89标准时必须在编译阶段确定大小,方便了程序的开发。查看上面例子对应的反汇编代码:
cpp
f(int):
push %rbp
mov %rsp,%rbp
sub $0x20,%rsp
mov %edi,-0x14(%rbp)
mov %rsp,%rax
mov %rax,%rcx
mov -0x14(%rbp),%eax
movslq %eax,%rdx
sub $0x1,%rdx
mov %rdx,-0x8(%rbp)
cltq
mov $0x10,%edx
sub $0x1,%rdx
add %rdx,%rax
mov $0x10,%esi
mov $0x0,%edx
div %rsi
imul $0x10,%rax,%rax
sub %rax,%rsp
mov %rsp,%rax
add $0x0,%rax
mov %rax,-0x10(%rbp)
mov -0x14(%rbp),%eax
sub $0x1,%eax
mov -0x10(%rbp),%rdx
cltq
movb $0x61,(%rdx,%rax,1)
mov -0x10(%rbp),%rax
movb $0x62,(%rax)
mov %rcx,%rsp
nop
leave
ret
从对a[n],a[0]赋值对应的汇编代码:
cpp
movb $0x61,(%rdx,%rax,1)
mov -0x10(%rbp),%rax
movb $0x62,(%rax)
可以看出数组a还是分配在栈上,这就意味着该数组的生命周期和其所在的作用域相同,当函数返回时,VLA的内存会被自动释放,不需要手动管理。
1. 内存分配
- 变长数组的内存分配通常在栈上进行,类似于普通的局部数组。
- 当函数被调用时,编译器会根据变长数组的大小为其分配相应的内存空间。
- 这种方式的优点是内存分配和释放的速度较快,但也有其局限性,比如栈空间有限。
2. 生命周期
- 变长数组的生命周期与其所在的作用域相同。当函数返回时,VLA的内存会被自动释放,不需要手动管理。
- 需要注意的是,VLA的大小不得为负或零,且应该在定义时必须是一个已知的变量。
3 编译器实现
- 编译器在处理变长数组时,通常会在生成的中间代码中包含对栈指针的相应调整,以适应变长数组的大小。
- 当编译器遇到变长数组时,会生成相应的代码来分配和管理内存。
4. 异常处理
- 如果在运行时,变长数组的大小超过了栈的可用空间(例如,过大的输入),可能会导致栈溢出。这是使用变长数组时需要注意的一个问题。
5. 变长数组的限制
- 变长数组的使用有限制,例如:
- 不能作为全局变量或静态变量。
- 不能用作函数的返回值。
- 不能在某些上下文中使用,比如作为数组的大小或初始化列表中。
6. 与动态内存分配的比较
- 变长数组和动态内存分配(如使用
malloc
)的区别在于:- VLA在栈上分配,速度快但受限于栈大小。
- 动态内存分配在堆上分配,灵活性高,但需要手动释放。