Reputation: 373042
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
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