Есть ли накладные расходы на использование массивов переменной длины?

Есть ли накладные расходы на использование массивов переменной длины? Может ли размер массива быть передан через аргумент командной строки во время выполнения? Почему это введено, по сравнению с автоматическим и динамическим размещением массива?

4 ответа

Решение

VLA имеет некоторые накладные расходы (по сравнению с "обычным" именованным массивом размера компиляции).

Во-первых, он имеет длительность выполнения, и все же язык предоставляет вам средства для получения фактического размера массива во время выполнения (используя sizeof). Это сразу означает, что фактический размер массива должен храниться где-то. Это приводит к незначительным накладным расходам памяти для каждого массива. Тем не менее, поскольку VLA могут быть объявлены только как автоматические объекты, эти накладные расходы памяти не то, что кто-либо когда-либо заметит. Это все равно что объявить дополнительную локальную переменную целочисленного типа.

Во-вторых, VLA обычно выделяется в стеке, но из-за его переменного размера в общем случае его точное местоположение в памяти не известно во время компиляции. По этой причине базовая реализация обычно должна реализовывать его как указатель на блок памяти. Это вводит некоторые дополнительные издержки памяти (для указателя), что опять-таки совершенно незначительно по причинам, описанным выше. Это также приводит к небольшому снижению производительности, так как мы должны прочитать значение указателя, чтобы найти фактический массив. Это те же накладные расходы, которые вы получаете при доступе malloc-ed массивы (и не получить с именованными массивами размера компиляции).

Поскольку размер VLA является целочисленным значением времени выполнения, его, конечно, можно передать в качестве аргумента командной строки. VLA не волнует, откуда его размер.

VLA были представлены как массивы времени выполнения с низкой стоимостью размещения / освобождения. Они помещаются между "обычными" именованными массивами размера компиляции (которые имеют практически нулевую стоимость выделения-освобождения, но фиксированный размер) и mallocмассивы (имеющие размер во время выполнения, но относительно высокие затраты на выделение-освобождение).

VLA подчиняется [почти] тем же правилам, зависящим от области действия, что и автоматические (то есть локальные) объекты, что означает, что в общем случае они не могут заменить mallocмассивы. Их применение ограничено ситуациями, когда вам нужен быстрый размерный массив с типичным автоматическим временем жизни.

Существуют некоторые накладные расходы во время выполнения с массивами переменной длины, но вам придется работать довольно усердно, чтобы измерить их. Обратите внимание, что sizeof(vla) не является константой времени компиляции, если vla является массивом переменной длины

Размер массива может быть передан функции во время выполнения. Если вы решите взять размер из аргумента командной строки, преобразовать его в целое число и передать его функции во время выполнения, пусть будет так - это будет работать.

Массивы переменной длины используются потому, что переменные автоматически присваиваются правильному размеру и автоматически освобождаются при выходе из функции. Это позволяет избежать перераспределения пространства (выделяя достаточно места для максимально возможного размера, когда вы в основном работаете с минимальными размерами) и избегает проблем с очисткой памяти.

Кроме того, с многомерными массивами, AFAIK, он ведет себя больше как Fortran - вы можете динамически настраивать все измерения, вместо того, чтобы застрять с фиксированными размерами для всех, кроме ведущего размера массива.


Конкретные доказательства некоторых накладных расходов во время выполнения для VLA - по крайней мере, с GCC 4.4.2 на SPARC (Solaris 10).

Рассмотрим два файла ниже:

vla.c - использование массива переменной длины

#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 - использование массива фиксированной длины

#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));
}

Компиляция и размеры объектных файлов

Для сравнения имена локального массива различны (vla против 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 существуют накладные расходы.

Мне просто интересно, есть ли некоторые издержки использования массивов переменной длины?

Нету

Может ли размер массива быть передан через аргумент командной строки во время выполнения?

Да.

Почему это введено, по сравнению с автоматическим и динамическим размещением массива?

Автоматическое выделение допускает только фиксированный размер, известный во время компиляции.

Динамически распределяя (malloc) будет хранить массив в куче, которая имеет большой объем памяти, но доступ к ней медленнее.

VLA работает, помещая массив в стек. Это делает выделение и доступ чрезвычайно быстрым, но стек обычно невелик (несколько килобайт), а когда VLA переполняет стек, он неотличим от бесконечной рекурсии.

Для VLA должно быть очень мало накладных расходов (самое большее это должно привести к добавлению к указателю стека). Динамическое выделение требует ручного управления памятью и медленнее, чем выделение VLA на основе стека, а для "автоматического" объявления массива требуется выражение времени компиляции для размера массива. Однако имейте в виду, что в случае переполнения стека это приведет к неопределенному поведению, поэтому держите VLA относительно небольшими.

Вы можете передать размер массива через аргумент командной строки, но вам придется написать код, чтобы справиться с этим самостоятельно.

Другие вопросы по тегам