Skip to content

LV050-变长数组

一、变长数组简介

在 C语言中,变长数组(Variable Length Array,简称 VLA)是一种可以在运行时动态确定大小的数组。变长数组是 C99 标准(ISO/IEC 9899:1999)引入的新特性,它允许数组的长度不是固定的编译时常量,而是根据程序运行时的变量或表达式来决定。

1. 什么是变长数组

在 C 语言里,传统的数组(如 int arr[10])要求在声明时指定一个固定的长度,这个长度必须是编译时已知的常量。而变长数组打破了这一限制,其大小可以在运行时动态计算,通常由变量或函数参数决定。

变长数组的内存分配仍然是在栈上完成的(与动态分配的堆内存不同),它的核心特点包括:

  • 大小在运行时确定。
  • 内存分配在栈上,自动管理生命周期。
  • 仅限于局部作用域(不能是全局变量)。

2. 怎么声明

变长数组的声明与普通数组类似,但长度部分可以用变量或表达式替代。基本语法如下:

c
// 直接定义
数据类型 数组名[表达式];

这里的 表达式 在运行时计算,确定数组的大小。例如:

c
int n = 5;
int arr[n]; // 变长数组,大小为 n

// 也可以在函数形参中确定大小
void process(size_t n) {
    int arr[n];  // VLA:长度为运行时变量n
    // ... 数组操作
}

需要注意的是,n 的值必须在数组声明时已经确定,且不能为负数或零,否则会导致未定义行为。

3. 使用实例

让我们通过一个简单示例来看看变长数组的实际应用。假设我们要根据用户输入的大小创建一个数组:

c
#include <stdio.h>

int main(int argc, char *argv[]) {
    int n = 5;
    int arr[n]; // 变长数组

    // 初始化数组
    for (int i = 0; i < n; i++) {
        arr[i] = i + 1;
    }

    // 打印数组
    printf("数组内容:");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

输出结果:

shell
数组内容:1 2 3 4 5

在这个例子中,arr 的大小由用户输入的 n 决定。程序根据 n 创建了一个变长数组,并对其进行初始化和输出。

4. 在函数中的应用

变长数组特别适合作为函数的局部变量,尤其在需要动态大小的场景中。例如,我们可以编写一个函数,根据参数创建变长数组并计算元素之和:

c
#include <stdio.h>
void sum_array(int size) {
    int arr[size]; // 变长数组
    for (int i = 0; i < size; i++) {
        arr[i] = i * 2;
    }
    int sum = 0;
    for (int i = 0; i < size; i++) {
        sum += arr[i];
    }
    printf("数组元素之和:%d\n", sum);
}
int main(int argc, char *argv[]) {
    int n = 5;
    sum_array(n);
    return 0;
}

输出结果:

shell
数组元素之和:20

这里,arr[size] 的长度由函数参数 size 决定,数组元素为 0、2、4、6,总和为 12。

5. 优点与局限性

5.1 优点

  • 简单易用:无需手动分配和释放内存。
  • 局部优化:栈上分配通常比堆上分配更快。
  • 动态性:无需提前知道固定大小。

5.2 局限性

  • 栈空间限制:栈大小有限(通常几 MB),过大的变长数组会导致栈溢出。
  • 仅限局部:不能声明为全局变量或静态变量。
  • 兼容性:C11 将 VLA 设为可选特性,部分编译器可能不支持。

5.3 注意事项

  • 大小检查:确保长度不为负数或零,避免未定义行为。
  • 栈溢出:避免声明过大的变长数组,例如 int arr[1000000] 可能崩溃。
  • 编译器支持:使用前确认编译器支持 C99 或更高版本(如 gcc 使用 -std=c99)。

示例(检测栈溢出风险):

c
#include <stdio.h>
int main() {
    int n = 1000000; // 过大可能溢出
    int arr[n];
    printf("创建了一个大小为 %d 的数组\n", n);
    return 0;
}

这种代码在小型栈环境中可能失败,需谨慎使用。

二、多维变长数组

C99 还支持多维变长数组,常用于动态矩阵等场景。声明时,至少有一个维度可以是运行时表达式。例如:

c
#include <stdio.h>
void print_matrix(int rows, int cols) {
    int matrix[rows][cols]; // 多维变长数组
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            matrix[i][j] = i + j;
        }
    }
    printf("矩阵内容:\n");
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", matrix[i][j]);
        }
        printf("\n");
    }
}
int main(int argc, char *argv[]) {
    print_matrix(3, 4);
    return 0;
}

输出结果:

shell
矩阵内容:
0 1 2 3 
1 2 3 4 
2 3 4 5

在这个例子中,matrix 是一个 3 行 4 列的变长数组,行数和列数由函数参数动态指定。

三、变长数组与动态内存分配的对比

变长数组常被拿来与动态内存分配(mallocfree)对比,二者都能实现动态大小的数组,但有显著区别。对比如下:

特性变长数组动态内存分配
内存位置
生命周期自动释放(离开作用域)需手动释放(free
大小限制受栈空间限制(较小)受堆空间限制(较大)
语法复杂度简单需手动管理

示例(动态内存分配版本):

c
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
    int n;
    printf("请输入数组大小:");
    scanf("%d", &n);
    int *arr = (int *)malloc(n * sizeof(int));
    for (int i = 0; i < n; i++) {
        arr[i] = i + 1;
    }
    printf("数组内容:");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    free(arr);
    return 0;
}

变长数组更简洁,但动态分配更灵活,适合大数组或需要跨作用域使用的情况。

四、总结

变长数组是 C99 标准新增的特性,它允许数组的长度不是固定的编译时常量,而是根据程序运行时的变量或表达式来决定,特别适合需要动态大小的局部数组场景。

参考资料:

C语言变长数组(VLA)的用法(非常详细,附带实例) - C语言中文网

可变长度数组(VLA)在C语言中的演进、争议与现代替代方案 | LuyangのBlog