Есть ли какие-то накладные расходы при использовании массивов переменной длины? Можно ли передать размер массива через аргумент командной строки во время выполнения? Зачем это введено, по сравнению с автоматическим и динамическим размещением массива?
Есть ли какие-либо накладные расходы на использование массивов переменной длины?
Ответы (4)
VLA имеет некоторые накладные расходы (по сравнению с «обычным» именованным массивом размера времени компиляции).
Во-первых, он имеет длину во время выполнения, и все же язык предоставляет вам средства для получения фактического размера массива во время выполнения (используя sizeof
). Это сразу означает, что фактический размер массива должен где-то храниться. Это приводит к незначительным накладным расходам памяти на каждый массив. Однако, поскольку VLA могут быть объявлены только как автоматические объекты, эти накладные расходы памяти никто никогда не заметит. Это все равно, что объявить дополнительную локальную переменную целочисленного типа.
Во-вторых, VLA обычно размещается в стеке, но из-за его переменного размера в общем случае его точное расположение в памяти неизвестно во время компиляции. По этой причине базовая реализация обычно должна реализовывать его как указатель на блок памяти. Это вносит некоторые дополнительные затраты памяти (для указателя), которые опять-таки совершенно несущественны по причинам, описанным выше. Это также приводит к небольшому снижению производительности, поскольку нам нужно прочитать значение указателя, чтобы найти фактический массив. Это те же накладные расходы, которые вы получаете при доступе к массивам malloc
(и не получаете с именованными массивами размера времени компиляции).
Поскольку размер VLA представляет собой целочисленное значение времени выполнения, его, конечно, можно передать в качестве аргумента командной строки. VLA не волнует, откуда берется его размер.
VLA были представлены как массивы размера во время выполнения с низкой стоимостью выделения/освобождения. Они находятся между «обычными» именованными массивами размера времени компиляции (которые имеют практически нулевую стоимость выделения-освобождения, но фиксированный размер) и массивами malloc
ed (которые имеют размер времени выполнения, но относительно высокую стоимость выделения-освобождения).
VLA подчиняются [почти] тем же правилам жизни, зависящим от области видимости, что и автоматические (т.е. локальные) объекты, а это означает, что в общем случае они не могут заменить malloc
-ed массивы. Их применимость ограничена ситуациями, когда вам нужен массив быстрого размера с типичным автоматическим временем жизни.
alloca
выделяет память прямо здесь, в стеке рядом с другими локальными переменными, а malloc
выделяет память где-то далеко, в куче.)
- person AnT; 05.08.2015
int (*a)[n] = malloc(sizeof(int[n]));
- person tstanisl; 05.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 возникают накладные расходы.
Мне просто интересно, есть ли какие-то накладные расходы на использование массивов переменной длины?
Неа
Можно ли передать размер массива через аргумент командной строки во время выполнения?
да.
Зачем это введено, по сравнению с автоматическим и динамическим размещением массива?
Автоматическое выделение позволяет только фиксированный размер, известный во время компиляции.
При динамическом размещении (malloc
) массив будет храниться в куче, которая имеет большой объем памяти, но доступ к которой осуществляется медленнее.
VLA работает, помещая массив в стек. Это делает выделение и доступ чрезвычайно быстрыми, но стек обычно мал (несколько КБ), и когда VLA переполняет стек, это неотличимо от бесконечной рекурсии.
n
, вы могли решить, подходит ли int a[n];
. Но это фундаментальная проблема C — вы даже не можете сказать, сможете ли вы успешно вызвать функцию. Ограничения реализации не подлежат запросу.
- person Kerrek SB; 17.10.2014
calloc()
для массива с отрицательным индексом в C — в частности, код с пометкой 2dv.c
.
- person Jonathan Leffler; 20.10.2014
malloc(sizeof(int[x][y]))
, не?
- person Kerrek SB; 20.10.2014
Для VLA должно быть очень мало накладных расходов (в лучшем случае это должно привести к добавлению к указателю стека). Динамическое выделение требует ручного управления памятью и медленнее, чем выделение VLA на основе стека, а «автоматическое» объявление массива требует выражения времени компиляции для размера массива. Однако имейте в виду, что если произойдет переполнение стека, это приведет к неопределенному поведению, поэтому держите VLA относительно небольшими.
Вы можете передать размер массива через аргумент командной строки, но вам придется написать код для обработки этого самостоятельно.