unscripted. home blog

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.