【问题标题】:C pointer to array/array of pointers disambiguation指向数组/指针数组的C指针消歧
【发布时间】:2010-10-25 23:15:41
【问题描述】:

以下声明有什么区别:

int* arr1[8];
int (*arr2)[8];
int *(arr3[8]);

理解更复杂的声明的一般规则是什么?

【问题讨论】:

  • 这是一篇关于阅读 C 中复杂声明的精彩文章:unixwiz.net/techtips/reading-cdecl.html
  • @jesper 不幸的是,在那篇文章中缺少constvolatile 限定符,它们既重要又棘手。

标签: c arrays pointers variable-declaration


【解决方案1】:
int* arr[8]; // An array of int pointers.
int (*arr)[8]; // A pointer to an array of integers

第三个和第一个一样。

一般规则是operator precedence。随着函数指针的出现,它会变得更加复杂。

【讨论】:

  • 所以,对于 32 位系统:int* arr[8]; /* 为每个指针 分配 8x4 字节/ int (*arr)[8]; / 分配了 4 个字节,只有一个指针 */
  • 不。 int* arr[8]:分配了 8x4 个字节total,每个指针 4 个字节。 int (*arr)[8] 是对的,4字节。
  • 我应该重新阅读我写的东西。我的意思是每个指针 4 个。感谢您的帮助!
  • 第一个和最后一个相同的原因是它总是允许在声明符周围加上括号。 P[N] 是一个数组声明符。 P(....) 是函数声明符, *P 是指针声明符。因此,以下所有内容都与没有任何括号相同(除了函数之一的“()”: int (((*p))); void ((g(void))); int *(a [1]); 无效 (*(p())).
  • 你的解释做得很好。有关运算符优先级和关联性的深入参考,请参阅 Brian Kernighan 和 Dennis Ritchie 的 The C Programming Language(ANSI C 第二版)第 53 页。运算符( ) [ ] 从左到右关联并且具有比*更高的优先级,因此将int* arr[8]读取为大小为8的数组,其中每个元素指向一个int,int (*arr)[8]作为指向大小为8的数组的指针整数
【解决方案2】:

按照 K&R 的建议,使用 cdecl 程序。

$ cdecl
Type `help' or `?' for help
cdecl> explain int* arr1[8];
declare arr1 as array 8 of pointer to int
cdecl> explain int (*arr2)[8]
declare arr2 as pointer to array 8 of int
cdecl> explain int *(arr3[8])
declare arr3 as array 8 of pointer to int
cdecl>

反之亦然。

cdecl> declare x as pointer to function(void) returning pointer to float
float *(*x)(void )

【讨论】:

  • @ankii 大多数 Linux 发行版都应该有一个包。您也可以构建自己的二进制文件。
  • 抱歉没提,这里是 macOS。将查看是否可用,否则网站也可以。 ^^ 感谢您让我知道这件事。请随意标记 NLN。
  • @ankii 你可以从 Homebrew 安装(也许是 MacPorts?)。如果这些不符合您的口味,那么从 cdecl.org 右上角的 Github 链接构建您自己的也是微不足道的(我刚刚在 macOS Mojave 上构建了它)。然后只需将 cdecl 二进制文件复制到您的 PATH 中。我推荐 $PATH/bin,因为没有必要让 root 参与这么简单的事情。
  • 哦,还没有阅读自述文件中关于安装的小段。只是一些用于处理依赖项的命令和标志.. 使用 brew 安装。 :)
【解决方案3】:

我不知道它是否有正式名称,但我称之为 Right-Left Thingy(TM)。

从变量开始,然后向右、向左、向右……以此类推。

int* arr1[8];

arr1 是一个包含 8 个整数指针的数组。

int (*arr2)[8];

arr2 是一个指向 8 个整数数组的指针(括号在左右两边)。

int *(arr3[8]);

arr3 是一个包含 8 个整数指针的数组。

这应该可以帮助您处理复杂的声明。

【讨论】:

  • 我听说它被称为“螺旋法则”,可以找到here
  • @InkBlend:螺旋规则不同于right-left ruleformer fails 在像 int *a[][10] 这样的情况下,而后者成功。
  • 正如 InkBlend 和 legends2k 所说,这是螺旋规则,它更复杂,并非在所有情况下都有效,所以没有理由使用它。
  • 不要忘记( ) [ ] 的从左到右的关联性和* & 的从右到左的关联性
  • @legends2k : int *a[][10] 的声明读数是什么
【解决方案4】:
int *a[4]; // Array of 4 pointers to int

int (*a)[4]; //a is a pointer to an integer array of size 4

int (*a[8])[5]; //a is an array of pointers to integer array of size 5 

【讨论】:

  • 第三个不应该是:a 是一个指向大小为 8 的整数数组的指针数组吗?我的意思是每个整数数组的大小都是 8 对吗?
  • @Rushil:不,最后一个下标([5])代表内部维度。这意味着(*a[8]) 是第一个维度,因此是数组的外部表示。 a 中的每个元素 指向 是一个大小为 5 的不同整数数组。
  • 感谢第三个。我正在寻找如何编写指向数组的指针数组。
【解决方案5】:

最后两个的答案也可以从C中的黄金法则中推导出来:

声明遵循使用。

int (*arr2)[8];

如果取消引用 arr2 会发生什么?你得到一个由 8 个整数组成的数组。

int *(arr3[8]);

如果你从arr3 中获取一个元素会发生什么?你得到一个指向整数的指针。

这在处理指向函数的指针时也很有帮助。以 sigjuice 为例:

float *(*x)(void )

当您取消引用 x 时会发生什么?你得到一个可以不带参数调用的函数。当你调用它时会发生什么?它将返回一个指向float 的指针。

不过,运算符优先级总是很棘手。然而,使用括号实际上也可能令人困惑,因为声明遵循使用。至少,对我来说,直观上arr2 看起来像是一个由 8 个指向 int 的指针组成的数组,但实际上恰恰相反。只是需要一些时间来适应。如果你问我,有足够的理由总是对这些声明添加评论:)

编辑:示例

顺便说一句,我刚刚偶然发现了以下情况:一个具有静态矩阵并使用指针算术来查看行指针是否超出范围的函数。示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NUM_ELEM(ar) (sizeof(ar) / sizeof((ar)[0]))

int *
put_off(const int newrow[2])
{
    static int mymatrix[3][2];
    static int (*rowp)[2] = mymatrix;
    int (* const border)[] = mymatrix + NUM_ELEM(mymatrix);

    memcpy(rowp, newrow, sizeof(*rowp));
    rowp += 1;
    if (rowp == border) {
        rowp = mymatrix;
    }

    return *rowp;
}

int
main(int argc, char *argv[])
{
    int i = 0;
    int row[2] = {0, 1};
    int *rout;

    for (i = 0; i &lt; 6; i++) {
        row[0] = i;
        row[1] += i;
        rout = put_off(row);
        printf("%d (%p): [%d, %d]\n", i, (void *) rout, rout[0], rout[1]);
    }

    return 0;
}

输出:

0 (0x804a02c): [0, 0]
1 (0x804a034): [0, 0]
2 (0x804a024): [0, 1]
3 (0x804a02c): [1, 2]
4 (0x804a034): [2, 4]
5 (0x804a024): [3, 7]

请注意,边框的值永远不会改变,因此编译器可以对其进行优化。这与您最初可能想要使用的不同:const int (*border)[3]: 将边界声明为指向 3 个整数数组的指针,只要变量存在,该数组就不会改变值。但是,该指针可以随时指向任何其他此类数组。相反,我们希望参数具有这种行为(因为此函数不会更改任何这些整数)。声明后使用。

(ps:请随时改进此示例!)

【讨论】:

    【解决方案6】:
    typedef int (*PointerToIntArray)[];
    typedef int *ArrayOfIntPointers[];
    

    【讨论】:

      【解决方案7】:

      根据经验,右一元运算符(如[]() 等)优先于左一元运算符。因此,int *(*ptr)()[]; 将是一个指向函数的指针,该函数返回指向 int 的指针数组(离开括号后尽快获取正确的运算符)

      【讨论】:

      • 这是真的,但它也是非法的。你不能有一个返回数组的函数。我试过了:error: ‘foo’ declared as function returning an array int foo(int arr_2[5][5])[5]; 在 GCC 8 下,$ gcc -std=c11 -pedantic-errors test.c
      • 编译器给出这个错误的原因是它把函数解释为返回一个数组,正如对优先规则的正确解释。它作为声明是非法的,但合法声明 int *(*ptr)(); 允许稍后使用像 p()[3](或 (*p)()[3])这样的表达式。
      • 好吧,如果我理解的话,你说的是创建一个函数,它返回一个指向数组第一个元素(不是数组本身)的指针,然后像返回一样使用该函数数组?有趣的想法。我会试试看。 int *foo(int arr_2[5][5]) { return &amp;(arr_2[2][0]); } 并这样称呼它:foo(arr)[4]; 应该包含 arr[2][4],对吧?
      • 对...但你也是对的,声明是非法的。 :)
      【解决方案8】:

      我认为我们可以使用简单的规则..

      example int * (*ptr)()[];
      start from ptr 
      

      " ptr 是指向 " 向右走..它的“)”现在向左走它的一个“(” 出来向右走“()”所以 "指向一个不带参数的函数"向左"并返回一个指针"向右" 一个整数数组“向左”

      【讨论】:

      • 我会稍微改进一下:“ptr 是一个名称,指的是”向右走……它是),现在向左走……它是*“指向”向右走……它是),现在向左走……它是一个(出来,向右走()所以“到一个不带参数的函数”向右走……[]“然后返回数组“向右;结束,所以向左...*”指向”向左...int“整数”
      【解决方案9】:

      这是一个有趣的网站,它解释了如何在 C 中读取复杂类型: http://www.unixwiz.net/techtips/reading-cdecl.html

      【讨论】:

        【解决方案10】:

        我是这样解释的:

        int *something[n];
        

        注意优先级:数组下标运算符 ([]) 的优先级高于 取消引用运算符 (*)。

        因此,这里我们将在* 之前应用[],使语句等效于:

        int *(something[i]);
        

        注意声明的意义:int num 表示 numintint *ptrint (*ptr) 表示,(ptr 处的值)是 一个int,它使ptr 成为一个指向int 的指针。

        这可以读作,((某物的第 i 个索引处的值)的值)是一个整数。因此,(某事物第 i 个索引处的值)是一个(整数指针),它使某事物成为整数指针数组。

        在第二个中,

        int (*something)[n];
        

        要理解这句话,你必须熟悉这个事实:

        数组指针表示注意事项:somethingElse[i]等价于*(somethingElse + i)

        因此,将somethingElse 替换为(*something),我们得到*(*something + i),根据声明它是一个整数。所以,(*something) 给了我们一个数组,它相当于 (指向数组的指针)

        【讨论】:

          【解决方案11】:

          我想第二个声明让很多人感到困惑。这是一个简单的方法来理解它。

          让我们有一个整数数组,即int B[8]

          让我们也有一个指向 B 的变量 A。现在,A 的值是 B,即(*A) == B。因此 A 指向一个整数数组。在您的问题中,arr 类似于 A。

          同样,在int* (*C) [8] 中,C 是一个指向整数指针数组的指针。

          【讨论】:

            【解决方案12】:
            int *arr1[5]
            

            在此声明中,arr1 是一个由 5 个指向整数的指针组成的数组。 原因:方括号的优先级高于 *(取消引用运算符)。 而在这种类型中,行数是固定的(这里是 5),但列数是可变的。

            int (*arr2)[5]
            

            在此声明中,arr2 是一个指向 5 个元素的整数数组的指针。 原因:这里,() 括号的优先级高于 []。 而在这种类型中,行数是可变的,但列数是固定的(这里是 5)。

            【讨论】:

              【解决方案13】:

              如果指针递增,则在指向整数的指针中,然后进入下一个整数。

              在指针数组中,如果指针递增,则跳转到下一个数组

              【讨论】:

              • "在指针数组中,如果指针递增,则跳转到下一个数组"这是完全错误的。
              猜你喜欢
              • 1970-01-01
              • 2017-08-25
              • 1970-01-01
              • 2013-04-19
              • 1970-01-01
              • 2023-03-11
              • 2021-02-07
              • 2016-07-30
              相关资源
              最近更新 更多