templatetypedef
templatetypedef

Reputation: 373042

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?

Upvotes: 19

Views: 2975

Answers (1)

Jonathan Leffler
Jonathan Leffler

Reputation: 754700

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
$

Upvotes: 13

Related Questions