难以理解高级指针算术语法

trouble understanding advanced pointer arithmatic syntax

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

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

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

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

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

指针算法如何使用它,例如,如果我们要增加 p

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

  2. 下一个代码的输出是 2 5,我认为它对上面显示的特殊语法有一个 link。有人可以向我解释为什么不是输出 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;
}

你应该阅读

 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;
}

&a 是 if 类型 int (*)[5],因此指针算法将遵守这一点。 &a+1指向整个数组之后的第一个元素。

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

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

  1. is it a regular pointer, or some kind of a special pointer to a block of memory that is 9*sizeof(int) big?

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

  1. how do I refer to such syntax?

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

  1. lets say I have a given matrix:
int mat[200][9];
int (*p)[9] = mat;

how would pointer arithmetic work with it, for example, if we were to increase p

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

  1. how do I cast to such type?

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

兼容
  1. the next code's output is 2 5 and I think that it has a link to the special syntax I've shown above. can someone explain to me why isn't the output 2 1?

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

此数组衰减规则的极少数例外之一是 & 地址运算符的操作数。所以在表达式 &a 中,数组 a 不会 衰减。相反,我们以指向数组 int (*)[5].

的指针的形式获取它的地址

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

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

指针声明的基本规则如下:

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

您可以通过替换来构建更复杂的指针类型。例如,如果你想声明一个 return 指向数组的指针的函数,你可以将它构建为

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

如果你想要一个指向return指向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 产生 a 之后 int 的下一个 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 个元素,您必须取消引用 pa 然后下标结果(假设 T 在这个例子中是 int):

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. 数组不是指针,但数组表达式会根据需要转换为指针表达式。