C Variable Length Arrays (VLA)
5/29/2025
The VLA concept exists in many languages but for the purpose of this article, we will discuss the C implementation.
VLA are variable length arrays, which means their size is unknown at compile time.
Let’s use a little example to understand what that means in C.
Note that it is for demonstration purposes and does not check for errors.
#include <stdlib.h>
int main(int argc, char **argv) {
size_t n = atoi(argv[1]);
int arr[n];
return 0;
}
Here, we use an argument passed to our program, store it into a size_t variable.
This variable is then used to declare an array arr
, a VLA.
After this, you could use the array like a normal one, assigning values, accessing specific indexes, etc…
You should now that the space needed for the array is allocated on the stack, at runtime.
This means when you leave the function, the access to the array is lost, like other stack-allocated variables.
I’m not fond of the VLA concept, as for me, the stack should be used for short, temporary, compile-time known size variables.
By the way, the Linux kernel is VLA free since many years.
VLA are NOT dynamic arrays though, like what you could do with malloc/realloc.
So how does this work ?
Well since we need to determine everything at runtime, the compiler adds additional instructions to reserve the space, everytime you declare a VLA.
Let’s see what the compiler generates for our little example.
I will comment directly in the asm code.
main:
; main function prologue
push rbp
mov rbp, rsp
push rbx
sub rsp, 56
; Saving argc, argv into the stack
mov DWORD PTR [rbp-52], edi
mov QWORD PTR [rbp-64], rsi
; Setup things to call atoi function
mov rax, rsp
mov rbx, rax
mov rax, QWORD PTR [rbp-64]
add rax, 8
mov rax, QWORD PTR [rax]
mov rdi, rax
call atoi
; Result of atoi is in rax, but it's a 32bit value
; cdqe transforms it into a 64bit value while keeping the sign
cdqe
; Saving atoi result into the variable n
mov QWORD PTR [rbp-24], rax
; Calculating n - 1
mov rax, QWORD PTR [rbp-24]
mov rdx, rax
sub rdx, 1
mov QWORD PTR [rbp-32], rdx
; rdx = n * 4
; it's the size in bytes of the VLA, sizeof(arr)
lea rdx, [0+rax*4]
; rax = 15
mov eax, 16
sub rax, 1
; rax = 15 + sizeof(arr)
add rax, rdx
; div rdx:rax / rcx
; quotient stored into rax
; remainder stored into rdx
; rdx:rax means concatenation of registers rdx and rax
; but here we set rdx to 0 (well edx but it sets the whole register)
; this means rax = rax / rcx = (15 + sizeof(arr)) / 16
; which means (:D) rax = ceil(sizeof(arr) / 16)
mov ecx, 16
mov edx, 0
div rcx
; multiply rax by 16 and store the result in rax
; tldr: rax *= 16
; which represents the closest multiple of 16 of sizeof(arr)
; so the space reserved is not really sizeof(arr)
; but the next multiple of 16
imul rax, rax, 16
; Reserving the space for our array
sub rsp, rax
; Getting the 4 bytes-aligned address of our array
; Why ? Because generally a variable of size n bytes,
; should be accessible on a n-bytes aligned address
; From the stack pointer
mov rax, rsp
; Adds 3
add rax, 3
; Divide by 4 (shift right >> 2)
shr rax, 2
; Multiply by 4 (shift left << 2)
sal rax, 2
; Now rax contains a 4bytes-aligned address
; That address is a bit higher than the rsp but remember
; we reserved more space than required so it's not a problem
; We store the address into the arr variable (remember arr is
; a pointer to the first cell of the array)
mov QWORD PTR [rbp-40], rax
; Cleanup / restore values / leave
mov eax, 0
mov rsp, rbx
mov rbx, QWORD PTR [rbp-8]
leave
ret
So that’s all for me, I hope that commented asm is understandable !
IMHO, you should not use VLAs, and they became optional in C11 which means not every compiler implements this feature.