C 中指向可变长度数组的指针的类型是什么?
What is the type of a pointer to a variable-length array in C?
这是一个简短的 C 程序,它提示用户输入一个数字,创建一个该大小的可变长度整数数组,然后使用指针算法来遍历分配的元素:
#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);
}
如果您愿意,可以试试这个 on ideone。输出表明行
void* ptr = (&arr) + 1;
获取 arr
的地址,并以大小感知的方式遍历可变长度数组中所有 n
元素。
如果这不是一个变长数组,我会完全适应它的工作原理。编译器会知道 arr
的类型(对于某个常量 K
它将是 int (*) [K]
),因此当我们将 1 添加到 &arr
时它可以跳过正确数量的字节。
很明显,在运行时,我们可以如何评估 (&arr) + 1
。编译器将 arr
的大小存储在堆栈的某处,当我们将 1 添加到 (&arr)
时,它知道加载该大小以计算要跳过的字节数。
但是,我不知道的是语言所说的表达式 &arr
的类型是什么。它是否分配了一些静态类型来表明它是一个可变长度数组(类似于 int (*) [??]
)?规范是否说明 "the type of the expression is int (*) [K]
, where K
is whatever size is assigned to the array at runtime?" 规范是否不允许获取可变长度数组的地址,而编译器恰好允许它?
对于 VLA,sizeof
运算符不是编译时常量。它在 运行 时评估。在您的问题中,&arr
的类型是 int (*)[n];
— 指向类型 int
的 n
值数组的指针,其中 n
是 运行-时间值。因此,正如您所注意到的,&arr + 1
(不需要括号,除了在括号注释周围指出不需要括号)是数组在 arr
之后的开始——地址是 sizeof(arr)
字节之外arr
.
的值
您可以打印 arr
的尺寸;它会给你合适的大小(printf()
修饰符z
)。您可以打印 &arr + 1
和 arr
之间的差异,您将获得大小作为 ptrdiff_t
(printf()
修饰符 t
)。
因此:
#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;
}
和两个示例运行s(程序名vla59
):
$ vla59
23
0x7ffee8106410
0x7ffee810646c
Size: 92
Diff: 92
$ vla59
16
0x7ffeeace7420
0x7ffeeace7460
Size: 64
Diff: 64
$
无需重新编译 — 但每次程序 运行 时 sizeof()
都是正确的。它是在 运行 时间计算的。
事实上,您甚至可以每次使用不同大小的循环:
#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;
}
样本 运行 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
$
这是一个简短的 C 程序,它提示用户输入一个数字,创建一个该大小的可变长度整数数组,然后使用指针算法来遍历分配的元素:
#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);
}
如果您愿意,可以试试这个 on ideone。输出表明行
void* ptr = (&arr) + 1;
获取 arr
的地址,并以大小感知的方式遍历可变长度数组中所有 n
元素。
如果这不是一个变长数组,我会完全适应它的工作原理。编译器会知道 arr
的类型(对于某个常量 K
它将是 int (*) [K]
),因此当我们将 1 添加到 &arr
时它可以跳过正确数量的字节。
很明显,在运行时,我们可以如何评估 (&arr) + 1
。编译器将 arr
的大小存储在堆栈的某处,当我们将 1 添加到 (&arr)
时,它知道加载该大小以计算要跳过的字节数。
但是,我不知道的是语言所说的表达式 &arr
的类型是什么。它是否分配了一些静态类型来表明它是一个可变长度数组(类似于 int (*) [??]
)?规范是否说明 "the type of the expression is int (*) [K]
, where K
is whatever size is assigned to the array at runtime?" 规范是否不允许获取可变长度数组的地址,而编译器恰好允许它?
对于 VLA,sizeof
运算符不是编译时常量。它在 运行 时评估。在您的问题中,&arr
的类型是 int (*)[n];
— 指向类型 int
的 n
值数组的指针,其中 n
是 运行-时间值。因此,正如您所注意到的,&arr + 1
(不需要括号,除了在括号注释周围指出不需要括号)是数组在 arr
之后的开始——地址是 sizeof(arr)
字节之外arr
.
您可以打印 arr
的尺寸;它会给你合适的大小(printf()
修饰符z
)。您可以打印 &arr + 1
和 arr
之间的差异,您将获得大小作为 ptrdiff_t
(printf()
修饰符 t
)。
因此:
#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;
}
和两个示例运行s(程序名vla59
):
$ vla59
23
0x7ffee8106410
0x7ffee810646c
Size: 92
Diff: 92
$ vla59
16
0x7ffeeace7420
0x7ffeeace7460
Size: 64
Diff: 64
$
无需重新编译 — 但每次程序 运行 时 sizeof()
都是正确的。它是在 运行 时间计算的。
事实上,您甚至可以每次使用不同大小的循环:
#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;
}
样本 运行 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
$