Search code examples
cpointerslanguage-lawyervariable-length-array

What is the type of a pointer to a variable-length array in C?


Here's a short C program that prompts the user for a number, creates a variable-length array of ints of that size, and then uses pointer arithmetic to step over the elements that were allocated:

#include <stdio.h>

int main() {
    /* Read a size from the user; inhibits compiler optimizations. */
    int n;
    scanf("%d", &n); // Yes, I should error-check. :-)

    /* We now have a VLA. */
    int arr[n];

    /* What is the type of &arr? */
    void* ptr = (&arr) + 1;

    /* Seems like this skipped over things properly... */
    printf("%p\n", arr);
    printf("%p\n", ptr);
}

You can try this on ideone if you'd like. The output suggests that the line

void* ptr = (&arr) + 1;

takes the address of arr and, in a size-aware way, steps over all n of the elements in the variable-length array.

If this were not a variable-length array, I'd be completely comfortable with how this works. The compiler would know the type of arr (it would be int (*) [K] for some constant K), so when we add one to &arr it could skip over the right number of bytes.

It's clear how, at runtime, we could evaluate (&arr) + 1. The compiler stashes the size of arr somewhere on the stack, and when we add one to (&arr) it knows to load that size in order to compute how many bytes to skip over.

However, what I don't know is what the language says the type of the expression &arr is. Is it assigned some static type that indicates it's a variable-length array (something like int (*) [??])? Does the spec say "the type of the expression is int (*) [K], where K is whatever size is assigned to the array at runtime?" Does the spec disallow taking the address of a variable-length array, and the compiler just happens to allow it?


Solution

  • With a VLA, the sizeof operator is not a compile-time constant. It is evaluated at run-time. In your question, the type of &arr is int (*)[n]; — a pointer to an array of n values of type int, where n is a run-time value. Hence, as you note, &arr + 1 (parentheses not needed except around parenthetical comments noting that the parentheses are not needed) is the start of the array after arr — the address is sizeof(arr) bytes beyond the value of arr.

    You could print the size of arr; it would give you the appropriate size (printf() modifier z). You could print the difference between &arr + 1 and arr and you'd get the size as a ptrdiff_t (printf() modifier t).

    Hence:

    #include <stdio.h>
    
    int main(void)
    {
        int n;
        if (scanf("%d", &n) == 1)
        {
            int arr[n];
    
            void* ptr = (&arr) + 1;
    
            printf("%p\n", arr);
            printf("%p\n", ptr);
            printf("Size: %zu\n", sizeof(arr));
            printf("Diff: %td\n", ptr - (void *)arr);
        }
        return 0;
    }
    

    And two sample runs (program name vla59):

    $ vla59
    23
    0x7ffee8106410
    0x7ffee810646c
    Size: 92
    Diff: 92
    $ vla59
    16
    0x7ffeeace7420
    0x7ffeeace7460
    Size: 64
    Diff: 64
    $
    

    No recompilation — but sizeof() is correct each time the program is run. It is calculated at run-time.

    Indeed, you can even use a loop with a different size each time:

    #include <stdio.h>
    
    int main(void)
    {
        int n;
        while (printf("Size: ") > 0 && scanf("%d", &n) == 1  && n > 0)
        {
            int arr[n];
    
            void* ptr = (&arr) + 1;
    
            printf("Base: %p\n", arr);
            printf("Next: %p\n", ptr);
            printf("Size: %zu\n", sizeof(arr));
            printf("Diff: %td\n", ptr - (void *)arr);
        }
        return 0;
    }
    

    Sample run of vla11:

    $ vla11
    Size: 23
    Base: 0x7ffee3e55410
    Next: 0x7ffee3e5546c
    Size: 92
    Diff: 92
    Size: 16
    Base: 0x7ffee3e55420
    Next: 0x7ffee3e55460
    Size: 64
    Diff: 64
    Size: 2234
    Base: 0x7ffee3e53180
    Next: 0x7ffee3e55468
    Size: 8936
    Diff: 8936
    Size: -1
    $