在C语言中,指向可变长度数组的指针类型为“指向元素类型的指针”。

18

这是一个简短的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);
}

如果您愿意,可以在ideone上尝试此代码。输出结果表明该行

void* ptr = (&arr) + 1;

arr的地址以一种大小感知的方式传递,并遍历可变长度数组中的所有n个元素。

如果这不是一个可变长度数组,我对它的工作原理完全感到满意。编译器将知道arr的类型(对于某个常量K,它将是int (*) [K]),因此当我们将&arr加1时,它可以跳过正确数量的字节。

很明显,在运行时,我们如何评估(&arr) + 1。编译器将arr的大小存储在堆栈的某个位置上,当我们将(&arr)加1时,它知道加载该大小以计算要跳过多少字节。

然而,我不知道语言规范如何描述表达式&arr的类型。它是否分配了某些静态类型,指示它是可变长度数组(类似于int (*) [??])?规范是否说“表达式的类型为int (*) [K],其中K是在运行时分配给数组的任何大小”?规范是否禁止获取可变长度数组的地址,而编译器只是偶然允许它?


形成指向可变长度数组的指针是绝对允许的;如果不能这样做,那么可变长度数组的可变长度数组等其他事情也将无法实现。 - user2357112
1
它将是指向可变长度数组类型的指针。sizeof运算符评估操作数以确定可变长度数组对象的大小,因此+必须执行相同的操作。请参见C.2011 - 6.5.6/10。 - jxh
我发布反汇编和反编译代码会有帮助吗?但我不知道该如何解释。 - savram
1
@savram 我的问题不在于它是如何工作的——机制对我来说相当清晰——而是关于C语言规范如何将类型分配给这里的表达式。我认为拆卸东西不会提供任何额外的见解。 - templatetypedef
我认为反编译的版本(而不是反汇编)会有所帮助,但如果你这么说,那我就不会发布它了。 - savram
显示剩余7条评论
1个回答

12
使用可变长度数组(VLA)时,sizeof 运算符不是编译时常量,而是在运行时计算的。在您的问题中,&arr 的类型是 int (*)[n]; — 指向一个由 n 个类型为 int 的值组成的数组的指针,其中 n 是运行时的值。因此,正如你所注意到的,&arr + 1 (除了括号内注明不需要括号的注释外,不需要其他括号)是在 arr 后面的数组的起始地址,该地址比 arr 的值大 sizeof(arr) 字节。
您可以打印 arr 的大小;它将给出适当的大小(printf() 修饰符 z)。您还可以打印 &arr + 1arr 的差异,并且会得到一个 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;
}

以下是两个样例运行结果(程序名称为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
$

1
是的。§6.5.3.4 sizeof和_Alignof运算符。(C11 n1570草案) - David C. Rankin
有趣。所以类型是“无论最终大小为何的数组”。太酷了!我不知道这一点。 - templatetypedef

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接