C中指向可变长度数组的指针的类型是啥?

Posted

技术标签:

【中文标题】C中指向可变长度数组的指针的类型是啥?【英文标题】:What is the type of a pointer to a variable-length array in C?C中指向可变长度数组的指针的类型是什么? 【发布时间】:2018-03-10 19:55:35 【问题描述】:

这是一个简短的 C 程序,它提示用户输入一个数字,创建一个由该大小的 int 组成的可变长度数组,然后使用指针算法遍历已分配的元素:

#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]),所以当我们在&amp;arr 上加一时,它可以跳过正确的字节数。

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

但是,我不知道该语言所说的表达式 &amp;arr 的类型是什么。它是否分配了一些静态类型,表明它是一个可变长度数组(类似于int (*) [??])?规范是否说“表达式的类型是int (*) [K],其中K 是在运行时分配给数组的任何大小?”规范是否不允许获取可变长度数组的地址,而编译器恰好允许它?

【问题讨论】:

绝对允许形成指向可变长度数组的指针;除此之外,如果你不能这样做,VLA 的 VLA 将无法工作。 参见 §6.5.6 第 10 段中的示例 The example rici is referring to. 它将是一个指向可变长度数组类型的指针。 sizeof 运算符评估操作数以确定可变长度数组对象的大小,因此 + 必须做同样的事情。参见 C.2011 - 6.5.6/10。 @savram 我的问题不是关于它是如何工作的——机制对我来说很清楚——更多的是关于 C 规范如何将类型分配给这里的表达式。我不认为分解东西会提供任何额外的洞察力。 【参考方案1】:

对于 VLA,sizeof 运算符不是编译时常量。它在运行时进行评估。在您的问题中,&amp;arr 的类型是 int (*)[n]; — 指向 n 类型为 int 的值的数组的指针,其中 n 是运行时值。因此,正如您所注意到的,&amp;arr + 1(不需要括号,除非括号内的 cmets 注意到不需要括号)是arr 之后数组的开头——地址是sizeof(arr) 超出arr 值的字节.

你可以打印arr的大小;它会给你合适的大小(printf() 修饰符z)。您可以打印&amp;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
$

【讨论】:

是的。 §6.5.3.4 sizeof 和 _Alignof 运算符。 (C11 n1570 草案) 有趣。所以类型是“一个无论它最终是什么大小的数组”。这很酷!我不知道。

以上是关于C中指向可变长度数组的指针的类型是啥?的主要内容,如果未能解决你的问题,请参考以下文章

c 复习

c 复习

c 复习

如何得到指针指向的数组的长度

在其内存应该已被释放后访问可变长度数组

在delphi中,这个PChar指针的作用是啥?