Массив переменной длины

В программировании массив переменной длины () представляет собой массив, длина которого определяется во время выполнения (а не во время компиляции).
В C массив переменной длины имеет управляемый переменной тип (), который зависит от какого-либо значения (см. Зависимый тип).

Основная цель массивов переменной длины — это упростить программирование численных алгоритмов.

Языки программирования, поддерживающие массивы переменной длины: Ada, Algol 68 (без возможности менять длину строк в двумерных массивах и т. д.), APL, C99 (хотя впоследствии массив переменной длины стал в C11 необязательной возможностью, поддержка которой не требуется; на некоторых платформах это могло быть реализовано ранее с помощью функции alloca() или аналогичных ей) и C# (массивы, выделенные на стеке — эта возможность доступна только в небезопасном режиме), COBOL, Fortran 90, J и Object Pascal (язык, используемый в средах Borland Delphi и Lazarus, компилирующийся с помощью Free Pascal Compiler).

Память

Выделение памяти 

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

Реализация

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 параметр длины должен предшествовать параметру массива переменной длины при вызовах функций. В C11 определяется макрос , если массивы переменной длины не поддерживаются. GCC имел массивы переменной длины в качестве расширения до C99, которое также распространяется на его диалект C++.

Линус Торвальдс в прошлом выражал своё недовольство использованием массивов переменной длины малых размеров, поскольку это порождает ассемблерный код более низкого качества. Ядро Linux 4.20 фактически не содержит массивов переменной длины.

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

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);
...

Ссылки 

Программирование