Массив переменной длины
В программировании массив переменной длины (англ. variable-length array, VLA, variable-sized array, runtime-sized array) представляет собой массив, длина которого определяется во время выполнения (а не во время компиляции)[1]. В C массив переменной длины имеет управляемый переменной тип (англ. variably modified type), который зависит от какого-либо значения (см. Зависимый тип).
Основная цель массивов переменной длины — это упростить программирование численных алгоритмов.
Языки программирования, поддерживающие массивы переменной длины: Ada, Algol 68 (без возможности менять длину строк в двумерных массивах и т. д.), APL, C99 (хотя впоследствии массив переменной длины стал в C11 необязательной возможностью, поддержка которой не требуется[2][3]; на некоторых платформах это могло быть реализовано ранее с помощью функции alloca()
или аналогичных ей) и C# (массивы, выделенные на стеке — эта возможность доступна только в небезопасном режиме), COBOL, Fortran 90, J и Object Pascal (язык, используемый в средах Borland Delphi и Lazarus, компилирующийся с помощью Free Pascal Compiler).
Память
правитьВыделение памяти
править- GNU C Compiler выделяет память для массива переменной длины с автоматическим сроком хранения (англ. automatic storage duration) на стеке[англ.][4]. Это более быстрый и простой вариант по сравнению с выделением в куче, и он используется большинством компиляторов.
- Массивы переменной длины также могут быть выделены в куче, внутри реализации используется указатель на этот блок.
Реализация
правитьC99
правитьСледующая функция на C99 выделяет массив переменной длины заданного размера, заполняет его значениями с плавающей запятой, а затем передаёт его другой функции для обработки. Поскольку массив объявлен как автоматическая переменная, его время жизни заканчивается, когда возвращается read_and_process()
.
float read_and_process(int n)
{
float vals[n];
for (int i = 0; i < n; ++i)
vals[i] = read_val();
return process(n, vals);
}
В C99 параметр длины должен предшествовать параметру массива переменной длины при вызовах функций[1]. В C11 определяется макрос __STDC_NO_VLA__
, если массивы переменной длины не поддерживаются[5]. GCC имел массивы переменной длины в качестве расширения до C99, которое также распространяется на его диалект C++.
Линус Торвальдс в прошлом выражал своё недовольство использованием массивов переменной длины малых размеров, поскольку это порождает ассемблерный код более низкого качества[6]. Ядро Linux 4.20 фактически не содержит массивов переменной длины[7].
Хотя C11 явно не указывает ограничение по размеру для массивов переменной длины, некоторые интерпретации полагают, что они должны иметь тот же максимальный размер, что и все другие объекты, т.е. SIZE_MAX
байт[8]. Однако такую интерпретацию следует понимать в более широком контексте ограничений среды и платформы, таких как типичный размер страницы с защитой стека 4 КиБ, что на много порядков меньше, чем SIZE_MAX
.
Можно воспользоваться синтаксисом, подобным массиву переменной длины, с динамическим хранением с помощью указателя на массив.
float read_and_process(int n)
{
float (*vals)[n] = malloc(sizeof(float[n]));
for (int i = 0; i < n; ++i)
(*vals)[i] = read_val();
float ret = process(n, *vals);
free(vals);
return ret;
}
Ada
правитьНиже приведён тот же пример на языке Ada. Массивы содержат свою длину вместе с данными, поэтому нет необходимости передавать их длину функции Process.
type Vals_Type is array (Positive range <>) of Float;
function Read_And_Process (N : Integer) return Float is
Vals : Vals_Type (1 .. N);
begin
for I in 1 .. N loop
Vals (I) := Read_Val;
end loop;
return Process (Vals);
end Read_And_Process;
Fortran 90
правитьЭквивалентная функция на языке Fortran 90.
function read_and_process(n) result(o)
integer,intent(in)::n
real::o
real,dimension(n)::vals
integer::i
do i = 1,n
vals(i) = read_val()
end do
o = process(vals)
end function read_and_process
Используется возможность Fortran 90 для проверки интерфейсов процедур во время компиляции; с другой стороны, если функции используют интерфейс вызова, который был до Fortran 90, сначала должны быть объявлены (внешние) функции, а длина массива должна быть явно передана в качестве аргумента (как в C):
function read_and_process(n) result(o)
integer,intent(in)::n
real::o
real,dimension(n)::vals
real::read_val, process
integer::i
do i = 1,n
vals(i) = read_val()
end do
o = process(vals,n)
end function read_and_process
Cobol
правитьСледующий фрагмент на языке COBOL объявляет массив записей переменной длины DEPT-PERSON
, имеющий длину (количество элементов), заданную значением PEOPLE-CNT
:
DATA DIVISION.
WORKING-STORAGE SECTION.
01 DEPT-PEOPLE.
05 PEOPLE-CNT PIC S9(4) BINARY.
05 DEPT-PERSON OCCURS 0 TO 20 TIMES DEPENDING ON PEOPLE-CNT.
10 PERSON-NAME PIC X(20).
10 PERSON-WAGE PIC S9(7)V99 PACKED-DECIMAL.
Массивы переменной длины в языке COBOL, в отличие от других языков, упомянутых здесь, безопасен, потому что COBOL требует указания максимального размера массива — в этом примере DEPT-PERSON
не может содержать более 20 элементов, независимо от значения PEOPLE-CNT
.
C#
правитьСледующий фрагмент на языке C# объявляет массив целых чисел переменной длины. До версии C# 7.2 требовался указатель на массив в «небезопасном» контексте. Ключевое слово unsafe
требует, чтобы сборка, содержащая этот код, была помечена как небезопасная.
unsafe void DeclareStackBasedArrayUnsafe(int size)
{
int *pArray = stackalloc int[size];
pArray[0] = 123;
}
C# версии 7.2 и более поздних версий позволяет выделять массив без ключевого слова unsafe
с помощью функции Span[9].
void DeclareStackBasedArraySafe(int size)
{
Span<int> stackArray = stackalloc int[size];
stackArray[0] = 123;
}
Object Pascal
правитьВ этом языке массив переменной длины называется динамическим массивом. Объявление такой переменной аналогично объявлению статического массива, но без указания его размера. Размер массива задаётся во время его использования.
program CreateDynamicArrayOfNumbers(Size: Integer);
var
NumberArray: array of LongWord;
begin
SetLength(NumberArray, Size);
NumberArray[0] := 2020;
end.
Удаление содержимого динамического массива выполняется путём присвоения ему нулевого размера.
...
SetLength(NumberArray, 0);
...
Ссылки
править- ↑ 1 2 Variable Length Arrays . Архивировано из оригинала 26 января 2018 года.
- ↑ Variable Length – Using the GNU Compiler Collection (GCC) . Дата обращения: 16 августа 2022. Архивировано 27 августа 2022 года.
- ↑ ISO 9899:2011 Programming Languages – C 6.7.6.2 4.
- ↑ Code Gen Options - The GNU Fortran Compiler . Дата обращения: 16 августа 2022. Архивировано 28 мая 2022 года.
- ↑ § 6.10.8.3 of the C11 standard (n1570.pdf)
- ↑ LKML: Linus Torvalds: Re: VLA removal (was Re: [RFC 2/2] lustre: use VLA_SAFE) . lkml.org. Дата обращения: 16 августа 2022. Архивировано 17 августа 2022 года.
- ↑ The Linux Kernel Is Now VLA-Free: A Win For Security, Less Overhead & Better For Clang - Phoronix (англ.). www.phoronix.com. Дата обращения: 16 августа 2022. Архивировано 23 июня 2022 года.
- ↑ §6.5.3.4 and §7.20.3 of the C11 standard (n1570.pdf)
- ↑ stackalloc operator (C# reference) . Microsoft. Дата обращения: 16 августа 2022. Архивировано 26 августа 2022 года.