指针表达式:**ptr++,*++*ptr和++**ptr的使用

31

我正在尝试学习C指针文献。在其中一张插图中,我遇到了以下代码。

# include <stdio.h>

int main()
{
     static int a[]={0,1,2,3,4};
     static int *p[]={a, a+1, a+2, a+3, a+4};

     int **ptr;

     ptr =p;

     **ptr++;
     printf("%d %d %d\n", ptr-p, *ptr-a, **ptr);

     *++*ptr; 
     printf("%d %d %d\n", ptr-p, *ptr-a, **ptr);

     ++**ptr;
     printf("%d %d %d\n", ptr-p, *ptr-a, **ptr);

     return 0;
}

我收到的输出为。

1 1 1
1 2 2
1 2 3

我面临一个问题,无法解释这个结果。我在一份副本上做了很多方框,以便更容易地理解问题。我能够证明输出为1 1 1,但我的困扰始于语句*++*ptr

由于一元操作符是从右到左执行的。所以,*ptr 将首先被处理,然后将增加 ptr 指向的值。完成此增量后,我不确定会发生什么,书中说某种方式增加了 p 来指向数组中的下一个元素。只有通过增加 p 才能获得输出 1 2 2

我不确定这种类型的问题是否完全适合于stackoverflow。
我已尽力了,浪费了至少10页纸并在上面画了方框。

任何澄清都将不胜感激。


检查 *++ 的优先级规则。 - Barmar
并且编译时开启警告 - 代码中存在一些无效的解引用操作。 - Carl Norum
2
@CarlNorum他并没有打印指针。他正在打印指针相减的结果,这些结果是整数。 - Barmar
3
*++*ptr中,我的理解是指对于*ptr所指向的值进行前缀自增,然后对该结果指针进行解引用以产生表达式值。但这是极其糟糕的代码,实际编码时应该避免使用。由于C语言指针的特性,这段代码可能会让人感到困惑和混乱。 - Hot Licks
3
@Barmar 确实- 评论已删除。然而,他也不应该使用%d%td是用于ptrdiff_t的正确格式字符串。 - Carl Norum
点已经被接受。 %d 不会在这里打印地址。如果我们假设两个指针是相同类型的,那么当我们减去它们时,我们得到的只是它们内存位置之间的差异,除以它们数据类型的大小。 - Gaurav
4个回答

54

请记住,在大多数表达式中,数组名可以很容易地变成指向第一个元素的指针(阅读一些数组名称不会衰变为指向第一个元素的指针的例外情况? 由@H2CO3 很好地回答)。
为了更好地理解,请考虑我的图示:

首先,假设a存储在内存中如下所示。

  a 
+----+----+----+----+---+
|  0 |  1 | 2  | 3  | 4 |
+----+----+----+----+---+
  ▲    ▲    ▲    ▲    ▲
  |    |    |    |    | 
  a    a+1  a+2  a+3  a+3

声明 static int *p[] = {a, a+1, a+2, a+3, a+4}; 创建了一个新的指向整数的指针数组,其具有以下值:

p[0] == a
p[1] == a + 1
p[2] == a + 2
p[3] == a + 3
p[4] == a + 4

现在,p也可以被认为是被存储在内存中的像下面这样的东西:
  p
+----+----+----+----+-----+
| a  |a +1| a+2| a+3| a+4 | 
+----+----+----+----+-----+
  ▲    ▲    ▲    ▲    ▲
  |    |    |    |    |
  p    p+1  p+2  p+3  p+4

在赋值ptr = p;之后,情况会变成这样:
  p                              a 
+----+----+----+----+-----+    +----+----+----+----+---+
| a  |a +1| a+2| a+3| a+4 |    |  0 |  1 | 2  | 3  | 4 |
+----+----+----+----+-----+    +----+----+----+----+---+
  ▲    ▲    ▲    ▲    ▲          ▲    ▲    ▲    ▲    ▲
  |    |    |    |    |          |    |    |    |    | 
  p    p+1  p+2  p+3  p+4        a    a+1  a+2  a+3  a+3
  ptr 


Notice: ptr points to first location in pointer array p[]

表达式:**ptr++;

现在我们考虑第一个 printf 语句之前的表达式 **ptr++;

  1. ptr等于指向指针数组第一个元素的地址 p。因此,ptr指向数组中的第一个元素p[0](或者我们可以说ptr == &p[0])。

  2. *ptr表示p[0],因为p[0]a,所以*ptra(因此*ptr == a)。

  3. 由于*ptra,那么**ptr*a == *(a + 0) == a[0],即0

  4. 注意,在表达式**ptr++;中,我们没有将其值分配给任何lhs变量。
    因此,**ptr++;的效果与ptr++;相同== ptr = ptr + 1 = p + 1
    这样,在此表达式之后,ptr指向p[1](或者我们可以说ptr == &p[1])。

打印-1:

第一个 printf 之前的事情变成了:

  p                              a 
+----+----+----+----+-----+    +----+----+----+----+---+
| a  | a+1| a+2| a+3| a+4 |    |  0 |  1 | 2  | 3  | 4 |
+----+----+----+----+-----+    +----+----+----+----+---+
  ▲    ▲    ▲    ▲    ▲          ▲    ▲    ▲    ▲    ▲
  |    |    |    |    |          |    |    |    |    | 
  p    p+1  p+2  p+3  p+4        a    a+1  a+2  a+3  a+3
       ptr 


Notice: ptr is equals to  p + 1 that means it points to p[1]

现在我们可以理解第一个printf:

  1. ptr - p 输出 1,因为:
    ptr = p + 1,所以 ptr - p == p + 1 - p == 1

  2. *ptr - a 输出 1,因为:
    ptr = p + 1,所以 *ptr == *(p + 1) == p[1] == a + 1
    这意味着:*ptr - a = a + 1 - a == 1

  3. **ptr 输出 1,因为:
    *ptr == a + 1 来自于第2点
    所以 **ptr == *(a + 1) == a[1] == 1

表达式: *++*ptr;

在第一个printf之后,我们有一个表达式*++*ptr;

如上面的第2点所述,*ptr == p [1]

因此,++*ptr(即++p [1])将增加p [1]a + 2

再次理解,在表达式*++*ptr;中,我们不将其值分配给任何lhs变量,因此*++*ptr;的效果只是++*ptr;

现在,在第二个printf之前,情况变为:

  p                              a 
+----+----+----+----+-----+    +----+----+----+----+---+
| a  |a+2 | a+2| a+3| a+4 |    |  0 |  1 | 2  | 3  | 4 |
+----+----+----+----+-----+    +----+----+----+----+---+
  ▲    ▲    ▲    ▲    ▲          ▲    ▲    ▲    ▲    ▲
  |    |    |    |    |          |    |    |    |    | 
  p    p+1  p+2  p+3  p+4        a    a+1  a+2  a+3  a+3
       ptr 


Notice: p[1] became a + 2 

打印-2:

现在我们可以理解 第二个 printf:

  1. ptr - p 的输出为 1,因为:
    ptr = p + 1,所以 ptr - p == p + 1 - p == 1

  2. *ptr - a 的输出为 2,因为:
    ptr = p + 1,所以 *ptr == *(p + 1) == p[1] == a + 2
    这意味着: *ptr - a == a + 2 - a == 2

  3. **ptr 的输出为 2,因为:
    *ptr == a + 2 来自于点-2
    所以 **ptr == *(a + 2) == a[2] == 2

表达式:++**ptr;

现在在第三个printf之前有一个表达式++**ptr;
正如我们从上面的第3点所知,**ptr == a[2]。 因此++**ptr == ++a[2]将会将a[2]增加到3
因此,在第三个printf之前的事情变成:
  p                              a 
+----+----+----+----+-----+    +----+----+----+----+---+
| a  | a+2| a+2| a+3| a+4 |    |  0 |  1 | 3  | 3  | 4 |
+----+----+----+----+-----+    +----+----+----+----+---+
  ▲    ▲    ▲    ▲    ▲          ▲    ▲    ▲    ▲    ▲
  |    |    |    |    |          |    |    |    |    | 
  p    p+1  p+2  p+3  p+4        a    a+1  a+2  a+3  a+3
       ptr 


 Notice: a[2] = 3

打印-3:

现在我们可以理解第三个printf:

  1. ptr - p 输出 1,因为:
    ptr = p + 1 所以 ptr - p == p + 1 - p == 1

  2. *ptr - a 输出 2,因为:
    ptr = p + 1 所以 *ptr == *(p + 1) == p[1] == a + 2
    这意味着:*ptr - a = a + 2 - a == 2

  3. **ptr 输出 3,因为:
    *ptr == a + 2 来自于第二个点
    所以 **ptr == *(a + 2) == a[2] == 3

编辑 注意:两个指针的差异具有类型 ptrdiff_t,因此正确的转换说明符是 %td,而不是 %d

另外一点:
我希望添加这一点,因为我相信对于新学习者会很有帮助

假设我们有以下两行代码,在 return 0; 前有一个额外的第四个 printf。

**++ptr;    // additional 
printf("%d %d %d\n", ptr-p, *ptr-a, **ptr);  // fourth printf

可以在Codepade上检查这个工作代码,这一行输出2 2 3

表达式:**++ptr;

因为ptr等于p + 1,所以在增量++操作后,ptr变成p + 2(或者我们可以说ptr == &p[2])。
之后双重引用操作** ==> **(p + 2) == *p[2] == *(a + 2) == a[2] == 3
现在,由于在这个语句中没有任何赋值操作,因此表达式**++ptr;的效果就是++ptr;

因此,在表达式**++ptr;之后,以下内容将如下图所示:

  p                              a 
+----+----+----+----+-----+    +----+----+----+----+---+
| a  | a+2| a+2| a+3| a+4 |    |  0 |  1 | 3  | 3  | 4 |
+----+----+----+----+-----+    +----+----+----+----+---+
  ▲    ▲    ▲    ▲    ▲          ▲    ▲    ▲    ▲    ▲
  |    |    |    |    |          |    |    |    |    | 
  p    p+1  p+2  p+3  p+4        a    a+1  a+2  a+3  a+3
            ptr 

 Notice: ptr is equals to  p + 2 that means it points to p[2] 

打印-4:

考虑到之前提到的 Forth printf,我加入了以下问题:

  1. ptr - p 的输出结果为 2,因为:
    ptr = p + 2,所以 ptr - p == p + 2 - p == 2

  2. *ptr - a 的输出结果为 2,因为:
    ptr = p + 2,所以 *ptr == *(p + 2) == p[2] == a + 2
    这意味着:*ptr - a = a + 2 - a == 2

  3. **ptr 的输出结果为 3,因为:
    *ptr 从上面的 point-2 中得知为 a + 2
    所以 **ptr == *(a + 2) == a[2] == 3


它没有解释第二行输出。 - Gaurav
2
它解释了一切。接受答案。谢谢。我想和你保持联系。 - Gaurav
4
这个优秀的回答让我意识到我可能永远无法更深层次地理解C语言;) - jpw
2
@GrijeshChauhan 您的回答非常出色。它解释了一切。我希望我可以多次点赞。 - Gaurav
非常出色的解释!在看到这个之前,我已经失去了自己解决问题的希望 :) - mr5

8

如果您在编译时开启了一些警告(clang甚至不需要任何标志),则会发现您的程序有三个多余的*运算符。简化您看起来很复杂的表达式为:

ptr++;
++*ptr;
++**ptr;

从这些操作中,你应该能够清楚地看到正在发生的事情:

  1. ptr++ 仅仅递增了 ptr,这使得它指向了 p 的第二个元素。在此操作之后,ptr - p 总是等于 1

  2. ++*ptr 递增了 ptr 所指向的位置上存储的值。这将把 p 的第二个元素指向了 a 的第三个元素,而不是第二个元素(初始状态下指向的是第二个元素)。这将使得 *ptr - a 等于 2。同样的,**ptr 是从 a 中获取的数字 2

  3. ++**ptr 递增了 ptr 所指向的位置上指向的位置所存储的值。这将会对 a 的第三个元素进行递增,并将其设置为 3


2

请记住++*的优先级更高,因此当您执行**ptr++时,它会增加指针并对旧值进行双重引用,如果它不是有效的指向指针的指针,则仅会导致崩溃,而且您的编译器(至少启用警告)应该警告您未使用的结果。

 static int a[]={0,1,2,3,4};

 static int *p[]={a, a+1, a+2, a+3, a+4};

 int **ptr;

 ptr = p; // ptr = &(p[0]); *ptr = a; **ptr = 0.

 **ptr++; // ptr = &(p[1]); *ptr = a+1; **ptr = 1
 printf("%d %d %d\n", ptr-p, *ptr-a, **ptr);

 *++*ptr; // ptr = &(p[1]); *ptr = a+2; **ptr = 2; p = {a, a+2, a+2, a+3, a+4} 
 printf("%d %d %d\n", ptr-p, *ptr-a, **ptr);

 ++**ptr; // ptr = &(p[1]); *ptr = a+2; **ptr = 3; a = {0, 1, 3, 3, 4}
 printf("%d %d %d\n", ptr-p, *ptr-a, **ptr);

Kevin,在你的回答中:*so when you do **ptr++, that increments the pointer and double-dereferences it,* 是不正确的!如果按照优先级规则理解,这对我来说也很困惑。但它并不是这样工作的。它首先进行解引用(*),然后是 =,然后是 ++(因为 ++ 是后缀运算符)。请查看这个可行的代码 - Grijesh Chauhan
1
没错,这会导致旧值被双重引用。我已经澄清了。 - Kevin

2
在地址为ptrint*值已经通过语句*++*ptr;(实际上是通过++*ptr部分,前导*未使用)进行了递增。因此,int *p[]的展开现在应该是这样的:int *p[]={a, a+2, a+2, a+3, a+4}; 最终的++**ptr;现在已经递增了地址为a+2的值,因此原始数组现在将如下所示:int a[]={0,1,3,3,4};

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