Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 325
templatetypedef Avatar asked Sep 28 '17 23:09

templatetypedef


People also ask

What is the type of a pointer array in C?

Array of Pointers in C. String Pointer in C. Function Pointer in C. Pointers with Structures in C. Pointer to Pointer in C.

What type of pointer is an array?

An array is considered to be the same thing as a pointer to the first item in the array. That rule has several consequences. An array of integers has type int*. C++ separates the issue of allocating an array from the issue of using an array.

What is a variable length array in C?

A variable length array, which is a C99 feature, is an array of automatic storage duration whose length is determined at run time.

Can you declare an array with a variable size in C?

size is a variable, and C does not allow you to declare (edit: C99 allows you to declare them, just not initialize them like you are doing) arrays with variable size like that. If you want to create an array whose size is a variable, use malloc or make the size a constant.


1 Answers

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
$
like image 80
Jonathan Leffler Avatar answered Sep 19 '22 07:09

Jonathan Leffler