【问题标题】:trouble understanding advanced pointer arithmatic syntax难以理解高级指针算术语法
【发布时间】:2020-12-17 13:01:25
【问题描述】:

假设我们得到了下一个设置:

int (*p)[9];
  1. 它是一个普通指针,还是某种指向9*sizeof(int)大内存块的特殊指针?

  2. 我如何引用这样的语法?

  3. 假设我有一个给定的矩阵:

int mat[200][9];
int (*p)[9] = mat;

指针算术如何处理它,例如,如果我们要增加 p

  1. 如何转换为这种类型?

  2. 下一个代码的输出是2 5,我认为它有一个指向我上面显示的特殊语法的链接。有人可以向我解释为什么不是输出2 1

int main()
{
    int a[5] = {1,2,3,4,5};
    int *ptr = (int*)(&a+1);
    printf("%d %d", *(a+1), *(ptr-1));
    return 0;
}

【问题讨论】:

    标签: arrays c pointers pointer-arithmetic


    【解决方案1】:

    你应该阅读

     int (*p)[9];
    

    作为指向int [9](9 个整数)数组的指针。它是一个常规指针,类型为int [9]

    指针算法的工作原理与其他指针的情况一样,基于指针的大小,即sizeof(int[9])

    也就是说,对于另一个问题,如有疑问,请检查数据类型!

    int main()
    {
        int a[5] = {1,2,3,4,5};
        int *ptr = (int*)(&a+1);               
        printf("%d %d", *(a+1), *(ptr-1));
        return 0;
    }
    

    &aint (*)[5] 的类型,所以指针算术将尊重它。 &a+1 指向整个数组之后的第一个元素。

    因此,ptr 很自然地指向最后一个元素地址之后的位置。所以,说*(ptr-1) 会得到最后一个元素的值,即5

    【讨论】:

      【解决方案2】:

      以下是指针声明的基本规则:

      T *p;              // p is a pointer to T
      T *ap[N];          // ap is an array of pointers to T
      T *fp();           // fp is a function returning a pointer to T
      
      T (*pa)[N];        // pa is a pointer to an array of T
      T (*pf)();         // pf is a function returning a pointer to T
      
      T * const p;       // p is a const pointer to T - *p is writable, but p is not
      const T *p;        // p is a non-const pointer to const T - p is writable, but *p is not
      T const *p;        // same as above
      const T * const p; // p is a const pointer to const T - neither p nor *p are writable
      T const * const p; // same as above
      

      要阅读一个毛茸茸的声明,找到最左边的标识符并按照上述规则解决问题,将它们递归地应用于任何函数参数。例如,下面是 C 标准库中 signal 函数的声明如何分解:

             signal                                       -- signal
             signal(                          )           -- is a function taking
             signal(    sig                   )           --   parameter sig
             signal(int sig                   )           --   is an int
             signal(int sig,        func      )           --   parameter func
             signal(int sig,      (*func)     )           --   is a pointer to
             signal(int sig,      (*func)(   ))           --     a function taking
             signal(int sig,      (*func)(   ))           --       unnamed parameter
             signal(int sig,      (*func)(int))           --       is an int
             signal(int sig, void (*func)(int))           --     returning void
           (*signal(int sig, void (*func)(int)))          -- returning a pointer to
           (*signal(int sig, void (*func)(int)))(   )     --   a function taking
           (*signal(int sig, void (*func)(int)))(   )     --     unnamed parameter
           (*signal(int sig, void (*func)(int)))(int)     --     is an int
      void (*signal(int sig, void (*func)(int)))(int);    --   returning void
      

      您可以通过替换构建更复杂的指针类型。例如,如果要声明一个返回指向数组的指针的函数,则可以将其构建为

      T     a     [N];    // a is an array of T
            |
        +---+----+
        |        |
      T (*  pa   )[N];    // pa is a pointer to an array of T 
            |
           ++--+
           |   |
      T (* fpa() )[N];    // fpa is a function returning a pointer to an array of T
      

      如果您想要一个指向函数的指针数组,这些函数返回指向T 的指针,那么您可以将其构建为

       T *     p           ;       // p is a pointer to T
               |
               +----------+
               |          |
       T *     fp        ();       // fp is a function returning pointer to T
               |
           +---+-----+
           |         |
       T * (*  pf    )   ();       // pf is a pointer to a function returning pointer to T
               |
              ++---+
              |    |
       T * (* apf[N] )   ();       // apf is an array of pointers to functions returning pointer to T
      

      指针运算是根据对象而不是字节来完成的。如果p 存储T 类型对象的地址,则p+1 产生该类型的下一个对象的地址。如果pa 存储T 的N 元素数组的地址,则pa+1 产生T 的下一个N 元素数组的地址。

      在您的代码中

      int main()
      {
          int a[5] = {1,2,3,4,5};
          int *ptr = (int*)(&a+1);
          printf("%d %d", *(a+1), *(ptr-1));
          return 0;
      }
      

      表达式&a + 1 产生int 跟在a 之后的下一个5 元素数组的地址。该地址值被强制转换为int *,因此它被视为a 的最后一个元素之后的第一个int 的地址。图表可能会有所帮助:

       int[5]        int           int *
       ------        ---           -----
              +---+        +---+        +---+
           a: |   |  a[0]: | 1 |   ptr: |   |
              + - +        +---+        +---+
              |   |  a[1]: | 2 |          |
              + - +        +---+          |
              |   |  a[2]: | 3 |          |
              + - +        +---+          |
              |   |  a[3]: | 4 |          |
              + - +        +---+          |
              |   |  a[4]: | 5 |          |
              +---+        +---+          |
       a + 1: |   |        | ? | <--------+
              + - +        +---+
              |   |        | ? |
              + - +        +---+
               ...          ...
      

      表达式aa+1 的类型为int [5],每个a[i] 的类型为intptr 的类型为int *

      因此,ptr-1 产生a[4] 的地址。


      请记住,C 声明语法反映了表达式语法 - 如果您有一个指向名为 pa 的数组的指针,并且您想要访问指向数组的第 i'th 元素,则必须取消引用 pa然后下标结果(假设Tint 对于这个例子):

      printf( "indexed value = %d\n", (*pa)[i] );
      

      在表达式和声明中,后缀运算符[]() 的优先级高于一元*,因此*pa[i] 将被解析为*(pa[i]),这不是我们在这种情况下想要的。我们需要明确地将* 运算符与pa 分组,这样我们才能取消引用正确的东西。

      表达式(*pa)[i]的类型是int,所以pa的声明写成

      int (*pa)[N];
      

      因此,声明的形状告诉您代码中表达式的形状。从那里它只是记住各种子表达式是如何键入的:

      Expression        Type
      ----------        ----
              pa        int (*)[N];
             *pa        int [N];
        (*pa)[i]        int
      

      数组下标操作a[i] 定义为*(a + i) - 给定起始地址a,偏移i 元素(不是字节 - 记住上面对指针运算的讨论)并取消引用结果。这意味着如果p是一个指针,那么

      *p == *(p + 0) == p[0]
      

      意味着您可以像数组一样为指针表达式下标1

      鉴于你的声明

      int mat[200][9];
      int (*p)[9] = mat;
      

      然后你可以索引到p,就像你可以索引到mat一样:

      (*p)[j] == (*(p + 0))[j] == p[0][j]
      

      因此p[i][j] 产生与mat[i][j] 相同的值。


      1. 数组不是指针,但数组表达式会根据需要转换为指针表达式。

      【讨论】:

        【解决方案3】:

        认为 &a 是一个指向具有 sizeof(int)*5 的“项目”的指针,所以当你加 1 时,它会将你发送到数组之后的下一个元素,该元素将提前 20 个字节的偏移量,所以您执行 -1 然后将其转换为 int 以获取数组的最后一个变量。

        【讨论】:

          【解决方案4】:
          1. 它是一个常规指针,还是某种指向 9*sizeof(int) 大内存块的特殊指针?

          它是一个指向int [9] 类型数组的指针。而这确实是9*sizeof(int) 字节大。

          1. 如何引用此类语法?

          它们被称为数组指针或指向数组的指针。您可以将其视为“指向整个数组的指针”,不要与“指向数组中第一个元素的指针”混淆。

          1. 假设我有一个给定的矩阵:
          int mat[200][9];
          int (*p)[9] = mat;
          

          指针算术如何处理它,例如,如果我们要增加 p

          就像任何指针类型一样,p 被分配为指向int [200][9] 数组中的第一个元素。第一个元素是数组int [9],指向此类数组的指针是int(*)[9]

          1. 如何转换为这种类型?

          与任何类型一样,尽管它们与指向不同大小或类型的数组的指针不兼容,也不与int* 兼容。

          1. 下一个代码的输出是2 5,我认为它有一个链接到我上面显示的特殊语法。有人可以向我解释为什么输出不是2 1

          通常,每当在表达式中使用数组时,数组都会衰减为指向第一个元素的指针。这就是 *(a+1) 发生的情况,a 衰减为 int* 并且指针算术 + 1 让我们指向第二个元素。

          此数组衰减规则的极少数例外之一是&amp; 地址运算符的操作数。所以在表达式&amp;a 中,数组a 确实衰减。相反,我们以指向数组int (*)[5] 的指针的形式获取它的地址。

          &amp;a + 1 然后对指向数组的指针进行指针运算。与任何指针算术一样,1 表示一个指向项的大小,在本例中为 sizeof(int [5])。所以我们最终会指向原始数组之一的边界外 - 这很好,只要我们不取消引用指针,我们就可以指向那里。

          (int*) 演员表非常可疑和不好的做法,因为(int*)int(*)[5] 不兼容。但是,只要我们不通过错误的类型取消引用数据,C 就允许我们进行狂野和可疑的强制转换。但是*(ptr-1) 然后转到数组的最后一项并取消引用它。这滥用了 C 指针/类型系统中的一个特殊规则,它允许我们通过不同的指针类型访问(“左值访问”)实际对象,只要该内存位置中存储的内容实际上是用于访问的类型.由于实际存储在物理内存地址上的5int,因此它可以工作。

          【讨论】:

          • 现在真正的问题是编写代码的人是否是 C 的老手,或者只是运气好。要编写这样的代码,您需要注意提到的两个特殊规则:指向数组末尾之后的 1 项,其中单个变量被视为包含 1 项的数组 - 以及有效的类型/严格指针别名规则.中等熟练的 C 程序员通常不会出现这种情况 - 如果您不了解上述规则,请避免使用狂野和危险的指针强制转换。
          • 谷歌搜索让我"Geek for geeks"。考虑到该网站的平均质量,我认为他们确实是靠运气做对了(尽管据我所知,链接页面上的所有内容似乎都是正确的)。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2021-02-03
          • 2011-06-22
          • 1970-01-01
          • 1970-01-01
          • 2017-11-12
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多