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

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


person Tim    schedule 09.01.2010    source источник


Ответы (4)


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

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

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

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

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

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

person AnT    schedule 09.01.2010
comment
VLA на самом деле подчиняются почти тем же правилам жизни, что и другие автоматические объекты (от объявления [VLA] до тех пор, пока выполнение программы не выйдет из области действия объявления, по сравнению с входом в блок, с которым связан [объект], до выполнения этот блок заканчивается каким-либо образом) [из 6.2.4(5) и 6.2.4(6) стандарта C99]. - person jjlin; 09.02.2012
comment
VLA обычно размещается в стеке, -- обычно? Вы имеете в виду, что он может быть выделен в куче? - person Spikatrix; 13.05.2015
comment
@Cool Guy: Я имею в виду, что спецификация языка не указывает, где они расположены, и даже не постулирует существование стека, поэтому я обычно предпочитаю добавлять различные словечки каждый раз, когда говорю о чем-то, что формально является реализацией. деталь. - person AnT; 13.05.2015
comment
После выделения есть ли разница между выделенной переменной malloc() и выделенной переменной alloca()? Например, загрузить/записать переменные - person dragonxlwang; 05.08.2015
comment
@dragonxlwang: После выделения нет никакой разницы. (Помимо таких соображений, как локальность памяти: alloca выделяет память прямо здесь, в стеке рядом с другими локальными переменными, а malloc выделяет память где-то далеко, в куче.) - person AnT; 05.08.2015
comment
@Spikatrix, да, динамический VLA можно создать с помощью int (*a)[n] = malloc(sizeof(int[n])); - person tstanisl; 05.05.2021
comment
@Spikatrix: stackoverflow.com/a/54163435/187690 - person AnT; 06.05.2021

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

person Jonathan Leffler    schedule 09.01.2010
comment
Строка vla[i][i] = 1; нуждается в дополнительном утверждении (n == m). Лучше было бы поставить vla[i][j] = ? я == j ? 1:0; во внутреннем цикле. YMMV. - person wildplasser; 08.10.2011

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

Неа

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

да.

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

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

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

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

person kennytm    schedule 09.01.2010
comment
Вау - ничья для сроков наших ответов! - person Jonathan Leffler; 09.01.2010
comment
И см. мой (исправленный) ответ для иллюстрации того, что существуют некоторые накладные расходы во время выполнения для использования VLA, по крайней мере, в некоторых реализациях компилятора (с использованием GCC 4.4.2 на Sun SPARC и Solaris 10 в качестве конкретного примера). - person Jonathan Leffler; 09.01.2010
comment
Нет причин думать, что доступ к куче медленнее. Выделение и освобождение памяти выполняются медленнее, чем выделение и освобождение стека (которые просто требуют корректировки указателя стека), но как только объект выделен, это просто еще один объект в памяти. - person Keith Thompson; 08.10.2011
comment
@KeithThompson: Хм, кеширование памяти? - person kennytm; 08.10.2011
comment
(Как) вы можете определить максимально допустимый размер для VLA и что произойдет, если вы его превысите? (Стандартные ссылки приветствуются.) - person Kerrek SB; 30.12.2013
comment
@KerrekSB: есть два случая: в стеке и в «куче». В стеке вы будете ограничены размером стека, который по умолчанию обычно составляет от 1 до 8 МБ (но вы можете установить ограничение больше). При выделении кучи хранилище данных ограничено, как и другая динамически выделяемая память — ограничение обычно довольно велико (но менее 4 ГиБ в 32-разрядной системе). - person Jonathan Leffler; 17.10.2014
comment
@JonathanLeffler: Ну, да, без сомнения, но я имею в виду, как вы можете понять это в программе, чтобы, когда пользователь вводит n, вы могли решить, подходит ли int a[n];. Но это фундаментальная проблема C — вы даже не можете сказать, сможете ли вы успешно вызвать функцию. Ограничения реализации не подлежат запросу. - person Kerrek SB; 17.10.2014
comment
@JonathanLeffler: Подождите, что, только куча? Как поместить VLA в кучу? - person Kerrek SB; 17.10.2014
comment
@KerrekSB: вы можете создать VLA в куче, как показано в моем ответе на calloc() для массива с отрицательным индексом в C — в частности, код с пометкой 2dv.c. - person Jonathan Leffler; 20.10.2014
comment
@JonathanLeffler: Ха. C такой странный :-) Но ваш код просто плохой слепок. Если вы хотите сделать это правильно, это должно быть что-то вроде malloc(sizeof(int[x][y])), не? - person Kerrek SB; 20.10.2014

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

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

person rlbond    schedule 09.01.2010