Variable-length array
A variable-length array (VLA), also known as a runtime-sized or variable-sized array, is an array data structure in programming whose length is not fixed at compile time but is instead determined by an expression evaluated at runtime.[1] This feature allows for flexible allocation of array space on the stack without the need for explicit dynamic memory management functions likemalloc and free.[2]
VLAs were formally introduced in the ISO C99 standard as a new array type to address the limitations of fixed-size arrays in prior versions like C89 and C90, enabling more portable and efficient handling of dynamically sized data in applications such as numerical computing and matrix operations.[2] In C99, they are declared using syntax such as int n = 10; int arr[n];, where the size n is a non-constant integer expression, and the array receives automatic storage duration, meaning it is allocated upon declaration and deallocated when the enclosing block or function scope ends.[1] VLAs were made optional in the C11 and C18 standards due to implementation complexities, potential for stack overflows from large runtime sizes, and challenges with features like goto statements that could bypass proper deallocation.[1] In the C23 standard (ISO/IEC 9899:2024), support for variably modified types (including VLA types) is mandatory, although automatic storage duration VLAs remain optional.[1]
Despite their optional status for automatic storage in recent standards, VLAs remain supported as an extension in compilers like GCC, which allows them in C90 mode and even in C++ for enhanced compatibility with legacy code.[3] Key advantages include simplified code for variable-sized buffers and multidimensional arrays passed as function parameters, such as void func(int n, int matrix[n][n]);, but limitations persist: VLAs cannot have static or external linkage, cannot be members of structures or unions, and their size must evaluate to a positive integer to avoid undefined behavior.[2] Similar concepts appear in other languages, such as Fortran's allocatable arrays or Ada's unconstrained array types, but the term "VLA" is most closely associated with C.[1]
Overview
Definition
A variable-length array (VLA), also known as a runtime-sized or variable-sized array, is an array data structure whose length is determined at runtime rather than at compile-time. This allows the array's dimensions to be specified using expressions evaluated during program execution, providing flexibility for scenarios where the required size cannot be predetermined statically. In contrast, fixed-size arrays require dimensions defined by constant expressions that are resolved at compile time, limiting their adaptability to known quantities.[2] VLAs maintain the contiguous memory layout typical of static arrays, ensuring elements are stored sequentially for efficient access and traversal. They belong to a broader category of variably modified types, where the size depends on runtime values, but unlike dynamically resizable structures, VLAs have a fixed length once declared. This design supports direct indexing and compatibility with array operations without additional indirection.[2] Common use cases for VLAs include temporary buffers in functions for processing variable amounts of data, such as reading input of unknown length, or matrices in numerical computations where dimensions, like the order of a transformation, are computed at runtime. These applications benefit from the ability to declare arrays locally without explicit dynamic allocation, simplifying code for tasks in scientific computing and data processing.[2]History
The concept of flexible or variable-length arrays originated in the 1960s with languages designed for scientific and systems programming. In PL/I, introduced in 1964, based variables allowed for dynamic allocation of storage, including arrays whose sizes could be determined at runtime, providing flexibility for list processing and variable-sized data structures.[4] Similarly, ALGOL 68, finalized in 1968, introduced flexible arrays as a core feature, enabling dynamic resizing of array variables to support evolving data needs, such as extending strings or other collections without fixed bounds at compile time.[5] Extensions for variable-length structures appeared in other languages during this period. COBOL, through its 1968 standard, incorporated the OCCURS DEPENDING ON clause, allowing tables (arrays) to have lengths determined at runtime based on a controlling data item, which facilitated handling of variable record sizes in business data processing.[6] Ada, standardized in 1983, supported unconstrained array types where bounds could be specified at runtime, promoting safe and modular handling of dynamic data in embedded and real-time systems.[7] The 1990s saw further standardization in numerical computing languages. Fortran 90, released in 1991, introduced allocatable arrays, permitting deferred-shape arrays whose sizes and bounds are allocated dynamically via the ALLOCATE statement, addressing limitations of fixed-size arrays in scientific simulations. In C, variable-length arrays (VLAs) were formally added in the C99 standard (ISO/IEC 9899:1999), primarily to simplify implementations of numerical algorithms by allowing runtime-sized automatic arrays and providing a portable alternative to the non-standard alloca function for stack allocation. Subsequent developments reflected mixed adoption and refinements. The C11 standard (ISO/IEC 9899:2011) made VLA support optional, acknowledging implementation challenges such as stack overflow risks and complexity in embedded environments, though creation of VLA objects with automatic storage duration remains the key optional aspect. Some compilers, including GCC, provide the -Wvla warning option for VLAs due to portability and security concerns like potential unbounded stack usage; this option has been available since GCC 4.0, though full deprecation has not occurred as of 2025. VLAs were never standardized in C++, where dynamic arrays are handled via std::vector from the C++98 standard onward for better safety and abstraction. The C23 standard (ISO/IEC 9899:2023) mandates support for variably modified types, including VLAs in function parameters, while keeping VLAs with automatic storage duration optional.[8]Key Characteristics
Memory Allocation
Variable-length arrays (VLAs) are primarily allocated on the call stack, where they are integrated into the function's activation record alongside other local variables.[9] This placement ensures that the array's memory is managed automatically by the runtime environment as part of the stack frame.[10] The allocation process begins with the computation of the array's size at runtime, often derived from function parameters or other dynamic values.[9] Upon function entry, the required space is reserved on the stack by adjusting the stack pointer, without the need for explicit allocation calls.[9] In contrast to heap allocation methods likemalloc in C, VLAs require no manual deallocation and avoid the overhead of separate memory management routines, though they are constrained by the stack's fixed size limit, typically 8 MB on many systems.[9][11]
For multidimensional VLAs, storage is provided contiguously in memory, with dimensions determined at runtime; for example, in C, a declaration such as int arr[n][m] where n and m are variables results in a block of memory sized according to the product of the dimensions and the element type.[9] However, exceeding the available stack space can lead to stack overflow, potentially causing program termination or undefined behavior.[10] Their lifetime is tied to the enclosing scope, with automatic deallocation upon exit.[9]
Lifetime Management
Variable-length arrays (VLAs) are automatically allocated upon entry to the block or function in which they are declared, with their storage duration tied to the automatic storage class. This allocation occurs as part of the standard stack frame setup, where the size of the VLA, determined at runtime, is added to the stack pointer adjustment at the point of declaration. Deallocation happens automatically upon exit from that scope, typically through stack unwinding during function return or block termination, ensuring the memory is reclaimed without explicit programmer intervention.[12] The scope of a VLA is strictly local to the block or function where it is declared, meaning it cannot be accessed outside that context. Attempting to return a pointer to a VLA from its declaring function results in a dangling pointer, as the array's memory is deallocated upon function exit, leading to undefined behavior if dereferenced afterward. This temporary nature enforces disciplined use, preventing accidental persistence of stack-allocated resources beyond their intended lifetime.[13] VLAs impose specific restrictions to maintain their runtime viability: the array size must be computable from values known prior to the declaration statement, ensuring the expression evaluates to a positive integer without forward references. In C, VLAs cannot serve as members of structures or unions, nor can they be used in initializers for other objects, as their variably modified types complicate compile-time analysis. Additionally, VLAs are confined to block scope or function prototype scope and lack external or static linkage.[12] In recursive functions, each invocation allocates its own independent VLA on the stack, which can accumulate and lead to stack exhaustion if the recursion depth is sufficient to exceed available stack space. This behavior mirrors the stack-based nature of automatic variables, where nested calls compound memory demands without bound.[14] Unlike heap-allocated arrays, which require manual allocation via functions likemalloc and explicit deallocation with free to manage persistence across scopes, VLAs eliminate the need for such operations, thereby mitigating risks of memory leaks from forgotten frees. However, this convenience comes at the cost of limited lifetime, as heap allocations can survive function boundaries and be returned safely, whereas VLAs cannot.
Advantages and Disadvantages
Advantages
Variable-length arrays (VLAs) provide significant benefits in scenarios requiring runtime-determined sizes, particularly in performance-critical and numerical applications. Their stack-based allocation enables faster allocation and deallocation than traditional heap-based dynamic memory, as it avoids the overhead of function calls such asmalloc and free, simply adjusting the stack pointer upon declaration and automatically reclaiming space upon scope exit.[15] This efficiency stems from the language standard's design to handle such arrays "in a convenient and efficient way" without manual intervention.[15]
Furthermore, VLAs benefit from improved cache locality due to their contiguous placement on the stack, near other local variables, which enhances data access speeds by increasing cache hit rates compared to scattered heap allocations.[16] Stack-allocated data inherently exhibits high locality, allowing even small caches to achieve high hit rates and reduce memory access latencies.[16]
In terms of simplicity, VLAs offer automatic lifetime management within local scopes, eliminating boilerplate code for memory handling and minimizing risks associated with manual deallocation errors.[15] This reduces programming complexity, as arrays are deallocated predictably at the end of the block without explicit cleanup.[15]
VLAs are especially suitable for numerical code, where they enable the creation of runtime-sized matrices or vectors without relying on pointers or dynamic allocation routines, simplifying algorithms like matrix multiplication that operate on variably dimensioned data.[15] For instance, in C99, a function can declare a two-dimensional VLA based on input parameters to perform operations directly on the array, avoiding the indirection of heap-allocated pointers. The feature addresses a key limitation in prior standards, making languages like C more viable for numerical computing by supporting arrays "whose size is known only at execution time."[15]
Built-in support in standards such as C99 enhances portability, allowing developers to use VLAs without resorting to compiler-specific extensions or non-standard libraries.[15] This standardization promotes consistent behavior across compliant implementations, facilitating code reuse in diverse environments.[15]
Disadvantages
One significant drawback of variable-length arrays (VLAs) is the risk of stack overflow, as they are allocated on the stack with automatic storage duration, and large sizes or deep recursion can exhaust available stack space, leading to program crashes. For instance, declaring a VLA of one million integers in C could consume approximately 4 MB of stack space, which exceeds typical stack limits on many systems (e.g., 1-8 MB), resulting in undefined behavior if the allocation fails due to insufficient resources.[17] This issue is particularly acute in embedded or resource-constrained environments where stack sizes are limited.[18] VLAs are inherently non-resizable; their size is determined at runtime upon declaration and remains fixed throughout their lifetime, unlike dynamic containers such as C++'sstd::vector that support resizing via reallocation.[18] The size expression is evaluated only once at the point of declaration (C11 §6.7.6.2), preventing any subsequent adjustments without recreating the array, which introduces inefficiency for scenarios requiring flexible growth.[19]
Portability concerns arise because VLAs are an optional feature in the C11 standard and remain optional for automatic storage declarations in the C23 standard (ISO/IEC 9899:2024), allowing implementations to omit support by defining the macro __STDC_NO_VLA__ as 1, although variably modified types are now mandatory.[18][20] Compilers like GCC issue warnings via the -Wvla flag to highlight potential issues, treating VLAs as extensions in pre-C99 modes, while others, such as Microsoft Visual C++, do not support them at all, rendering code non-compilable across environments.[17]
Debugging VLAs presents challenges due to their runtime-determined sizes, which hinder static analysis tools and compilers from performing thorough bounds checking or optimization.[17] If the size evaluates to zero or a negative value, the behavior is undefined per the C standard (C11 §6.7.6.2 and §J.2), potentially causing segmentation faults or erratic program execution without clear diagnostics.[18] This variability complicates error detection, as tools must account for dynamic dimensions rather than fixed ones.
Finally, VLAs are not suitable for persistent data, as their automatic storage duration ties their lifetime to the declaring scope, deallocating them upon function or block exit without the option for explicit persistence.[18] To retain VLA contents beyond this scope, programmers must manually copy the data to heap-allocated memory using functions like malloc, adding overhead and risk of memory leaks if not managed properly (C11 §6.2.4).[19]
Implementations
In C
Variable-length arrays (VLAs) were introduced in the C99 standard as a feature allowing the declaration of arrays with sizes determined at runtime rather than compile time. The basic syntax involves using a runtime-evaluated integer expression as the array size, such asint arr[n]; where n is a variable or expression of integer type. This allocation occurs automatically on the stack when the declaration is reached, and the array is deallocated upon exiting the containing block scope.[1]
Multidimensional VLAs are also supported, enabling declarations like int matrix[n][m]; where both n and m can be runtime values. Such arrays are stored contiguously in memory, similar to fixed-size multidimensional arrays, facilitating efficient access patterns in numerical computations.[1]
VLAs come with several restrictions to ensure portability and safety. The size expression must evaluate to a positive integer value greater than zero at runtime; a zero or negative size results in undefined behavior. VLAs must have automatic storage duration and cannot be declared at file scope, possess external or static linkage, or serve as members of structures or unions. For function parameters, direct VLA types are not permitted—instead, equivalent pointer types are used, as in void func(int n, int arr[n]); which adjusts to void func(int n, int *arr); in the function prototype, treating arr as a pointer to the first element.[1]
The C11 standard made VLA support optional for implementations, with the preprocessor macro __STDC_NO_VLA__ defined to 1 if VLAs of automatic storage duration are not provided (though variably modified types and dynamically allocated VLAs remain required). Compilers such as GCC enable VLA support by default in C99 and later modes via flags like -std=c99 or -std=gnu99, but stricter conformance modes may require explicit specification.[1][21]
A notable pitfall involves the sizeof operator applied to VLA types: within a function prototype scope, sizeof a VLA parameter yields the size of a pointer to the array's element type rather than the complete array size based on n. Side effects in the size expression, such as function calls, are not evaluated in sizeof contexts. Furthermore, modern compilers often issue warnings about VLA usage due to risks like stack overflows from excessively large runtime sizes; for instance, GCC's -Wvla option (enabled under -pedantic for pre-C99 modes) alerts developers to their presence, and -Wvla-larger-than=bytes can flag allocations exceeding a specified limit.[1][17]
In Ada
In Ada, support for variable-length arrays has been available since the language's initial standardization in Ada 83, allowing arrays whose bounds are determined at runtime rather than compile time.[22] This is achieved through unconstrained array types, declared without fixed index bounds using the box notation<> to indicate variability.[23] For example, a subtype for the length can be defined as subtype Length is Positive;, followed by a declaration like Arr: array(1..N) of Integer; where N is a runtime variable of type Length.[23] Such arrays are instantiated when the object is created, with bounds specified at that point, enabling flexible sizing based on program needs.[23]
Unconstrained arrays provide runtime instantiation, where the index subtype is discrete but the specific bounds vary per object.[23] A type declaration might look like type [Vector](/page/Vector) is [array](/page/Array)(Positive [range](/page/Range) <>) of Real;, and an object can then be created with runtime bounds, such as within a declare block for stack allocation: declare V: [Vector](/page/Vector)(1..N); where N is computed at execution time.[23] For dynamic allocation on the heap, access types are used in conjunction with the new operator, allowing pointers to unconstrained arrays, as in type Vector_Access is [access](/page/Access) [Vector](/page/Vector); V_Ptr: Vector_Access := new [Vector](/page/Vector)'(1..N => 0.0);.[24] This approach supports stack-based variable-length arrays via local declare blocks while enabling heap allocation for longer lifetimes.[23]
Ada emphasizes safety in handling variable-length arrays through built-in runtime checks for index validity, ensuring that subscripting operations respect the array's bounds and preventing buffer overflows.[23] These checks are automatically enforced by the compiler-generated code, raising a Constraint_Error if violated. Additionally, arrays integrate seamlessly with Ada's tasking model for concurrency, where protected objects or rendezvous can safely share array data across tasks without race conditions, leveraging the language's strong typing and synchronization primitives.
Unlike fixed arrays, which have compile-time constant bounds (e.g., type Fixed_Table is array(1..10) of Integer;), variable-length arrays in Ada leverage unconstrained types for runtime flexibility.[23] A key distinction arises in record types, where discriminants can parameterize array components to vary their size based on the record's value, as in type Variable_Record (Size: Positive) is record Data: array(1..Size) of [Integer](/page/Integer); end record;.[25] This discriminant mechanism allows compile-time knowledge of variability while deferring actual bounds to runtime instantiation, enhancing type safety over purely fixed structures.[25]
In Fortran
In Fortran 90, variable-length arrays are primarily implemented through allocatable arrays, which allow dynamic memory allocation at runtime using the ALLOCATE statement. An allocatable array is declared with the ALLOCATABLE attribute, specifying deferred shape bounds, such asreal, allocatable :: arr(:). The array's size is then set explicitly with allocate(arr(n)), where n is a runtime expression determining the extent. This mechanism supports flexible array dimensions without requiring fixed sizes at compile time, enabling efficient handling of data whose size is unknown until execution.[26]
Allocatable arrays are typically allocated on the heap, providing scalability for large datasets and avoiding stack overflow issues common with automatic arrays declared using runtime expressions in procedure scopes. In contrast, automatic arrays reside on the stack and are deallocated upon procedure exit, but they are limited by stack size constraints. Deallocation of allocatable arrays is performed explicitly with the DEALLOCATE statement, such as deallocate(arr), to free memory and prevent leaks; failure to do so can lead to resource exhaustion in long-running programs. Multidimensional allocatable arrays follow the same pattern, with deferred shapes like real, allocatable :: matrix(:,:) allocated as allocate(matrix(m, n)), where m and n are runtime values, facilitating operations on matrices or tensors with variable dimensions.[27][28]
Since Fortran 90, local allocatable arrays without the SAVE attribute are automatically deallocated upon leaving the scope where they are declared.[29] Fortran 2003 introduced enhancements such as the SOURCE= specifier in ALLOCATE, allowing initialization from another array, e.g., allocate(b, source=a). Pointer arrays, also available since Fortran 90, serve as an alternative for dynamic allocation but require explicit association and disassociation, making them more prone to aliasing errors. Allocatable arrays are generally preferred for data ownership in modern Fortran code due to their automatic memory management and contiguous storage guarantees, which improve performance in numerical algorithms.[28][30]
In numerical libraries and scientific simulations, allocatable arrays are extensively used to manage structures with variable sizes, such as sparse matrices in linear algebra routines or grids in computational fluid dynamics. For instance, the Fortran Standard Library's sparse module employs allocatable arrays to represent compressed sparse row formats, enabling efficient storage and operations on matrices with predominantly zero elements. Similarly, geotechnical simulation tools like TRIGRS leverage allocatable arrays for dynamic allocation of slope profiles and grid data, accommodating arbitrary problem sizes without recompilation.[31][32]
In Other Languages
In COBOL, variable-length tables are supported through the OCCURS DEPENDING ON clause, which allows the number of occurrences to be determined at runtime based on a specified data item. For example, a table can be declared as01 TABLE OCCURS 1 TO 100 DEPENDING ON N TIMES, where N defines the actual length during execution.[33][34]
In C#, arrays are fixed-size once created, such as int[] arr = new int[n]; where n is evaluated at runtime but cannot be changed afterward. True variable-length array behavior can be achieved using unsafe code with pointers or through spans for bounded views, though the language encourages List<T> for dynamically resizable collections.[35][36]
Object Pascal, as implemented in Delphi, supports dynamic arrays declared with an open-ended syntax like type TArray = array of Integer;, which are allocated on the heap and resized at runtime using the SetLength procedure, such as SetLength(arr, n);.[37][38]
Other languages offer varying support for variable-length arrays; PL/I provides variable storage through controlled variables for adjustable extents in large aggregates, while Modula-2 uses open arrays in procedure parameters to handle bounds determined by the caller. Java lacks native variable-length arrays, relying on reflection methods like Array.newInstance for dynamic creation or collections like ArrayList as alternatives, and Python uses lists as inherently dynamic sequences that grow or shrink as needed.[39][40][41][42]