使用可变长度数组有任何开销吗?

问题描述 投票:45回答:4

使用可变长度数组有一些开销吗?数组的大小是否可以在运行时通过命令行参数传递?与自动和动态分配数组相比,为什么会引入它?

c arrays variable-length-array
4个回答
43
投票

VLA确实有一些开销(与“普通”命名的编译时大小的数组相比)。

首先,它具有运行时长度,但语言为您提供了在运行时获取数组实际大小的方法(使用sizeof)。这立即意味着数组的实际大小必须存储在某处。这导致一些无关紧要的每阵列内存开销。但是,由于VLA只能被声明为自动对象,因此这种内存开销并不是任何人都会注意到的。这就像声明一个整数类型的额外局部变量。

其次,VLA通常在堆栈上分配,但由于其可变大小,一般情况下,它在内存中的确切位置在编译时是未知的。因此,底层实现通常必须将其实现为指向内存块的指针。这引入了一些额外的内存开销(对于指针),由于上述原因,这又是完全无关紧要的。这也会带来轻微的性能开销,因为我们必须读取指针值才能找到实际的数组。这与访问malloc-ed数组时获得的开销相同(并且不会使用命名的编译时大小的数组)。

由于VLA的大小是运行时整数值,因此它当然可以作为命令行参数传递。 VLA并不关心它的大小来自哪里。

VLA是作为运行时大小的阵列引入的,具有低分配/解除分配成本。它们适合“普通”命名的编译时大小的数组(几乎没有分配 - 释放成本,但是固定大小)和malloc-ed数组(具有运行时大小,但分配 - 释放成本相对较高)。

VLA遵循[几乎]与自动(即本地)对象相同的与范围相关的生命周期规则,这意味着在一般情况下它们不能取代malloc-ed数组。它们的适用性仅限于需要具有典型自动生命周期的快速运行时大小的阵列的情况。


28
投票

使用可变长度数组会有一些运行时开销,但您必须非常努力地测量它。请注意,如果sizeof(vla)是可变长度数组,则vla不是编译时常量。

数组的大小可以在运行时传递给函数。如果您选择从命令行参数获取大小并将其转换为整数并在运行时将其传递给函数,那么就可以了 - 它会起作用。

使用可变长度数组是因为变量会自动分配到正确的大小,并在退出函数时自动释放。这样可以避免过度分配空间(当您使用最小的大小时,为最大可能的大小分配足够的空间),并避免内存清理问题。

此外,对于多维数组,AFAIK的行为更像Fortran - 您可以动态配置所有维度,而不是固定大小除了阵列的主要维度之外的所有维度。


VLA的一些运行时开销的具体证据 - 至少在SPARC(Solaris 10)上使用GCC 4.4.2。

考虑以下两个文件:

vla.c - using a variable-length array

#include <assert.h>
#include <stddef.h>
extern size_t identity_matrix(int n, int m);

size_t identity_matrix(int n, int m)
{
    int vla[n][m];
    int i, j;
    assert(n > 0 && n <= 32);
    assert(m > 0 && m <= 32);
    for (i = 0; i < n; i++)
    {
        for (j = 0; j < m; j++)
        {
            vla[i][j] = 0;
        }
        vla[i][i] = 1;
    }
    return(sizeof(vla));
}

fla.c - using a fixed-length array

#include <assert.h>
#include <stddef.h>
extern size_t identity_matrix(int n, int m);

size_t identity_matrix(int n, int m)
{
    int fla[32][32];
    int i, j;
    assert(n > 0 && n <= 32);
    assert(m > 0 && m <= 32);
    for (i = 0; i < n; i++)
    {
        for (j = 0; j < m; j++)
        {
            fla[i][j] = 0;
        }
        fla[i][i] = 1;
    }
    return(sizeof(fla));
}

Compilation and object file sizes

为了进行比较,本地数组的名称是不同的(vla vs fla),并且声明时数组上的维度是不同的 - 否则,文件是相同的。

我编译使用:

$ gcc -O2 -c -std=c99 fla.c vla.c

目标文件大小有些不同 - 由“ls”和“size”测量:

$ ls -l fla.o vla.o
-rw-r--r--   1 jleffler rd          1036 Jan  9 12:13 fla.o
-rw-r--r--   1 jleffler rd          1176 Jan  9 12:13 vla.o
$ size fla.o vla.o
fla.o: 530 + 0 + 0 = 530
vla.o: 670 + 0 + 0 = 670

我没有做过大量的测试,看看有多少开销是固定的,多少是可变的,但是使用VLA会有开销。


4
投票

我只是想知道使用可变长度数组是否有一些开销?

可以在运行时通过命令行参数传递数组的大小吗?

是。

与自动和动态分配数组相比,为什么会引入它?

自动分配仅允许在编译时已知的固定大小。

动态分配(malloc)会将数组存储在堆上,堆具有较大的内存空间,但访问速度较慢。

VLA的工作原理是将数组放入堆栈中。这使得分配和访问非常快,但堆栈通常很小(几KB),当VLA溢出堆栈时,它与无限递归无法区分。


1
投票

对于VLA,开销应该非常少(最多应该导致堆栈指针的添加)。动态分配需要手动内存管理,并且比基于堆栈的VLA分配慢,并且数组的“自动”声明需要数组大小的编译时表达式。但是,请记住,如果发生堆栈溢出,则会导致未定义的行为,因此请保持相对较小的VLA。

您可以通过命令行参数传递数组的大小,但您必须编写代码来自己处理。

© www.soinside.com 2019 - 2024. All rights reserved.