双指针输出说明
double pointer output explanation
你能解释一下输出是如何 -4
的吗?我认为 ++pp;
是 UB 但不确定。您的解释将真正有助于理解。 big-endian 或 little-endian 机器的输出有什么不同吗?
#include <stdio.h>
int a[] = { -1, -2, -3, -4 };
int b[] = { 0, 1, 2, 3 };
int main(void)
{
int *p[] = { a, b };
int **pp = p;
printf("a=%p, b=%p, p=%p, pp=%p\n", (void*)a, (void*)b, (void*)p, (void*)pp);
++pp;
printf("p=%p, pp=%p *pp=%p\n", (void*)p, (void*)pp, (void*)*pp);
++*pp;
printf("p=%p, pp=%p *pp=%p\n", (void*)p, (void*)pp, (void*)*pp);
++**pp;
printf("%d\n", (++**pp)[a]);
}
我的输出:
a=0x107121040, b=0x107121050, p=0x7ffee8adfad0, pp=0x7ffee8adfad0
p=0x7ffee8adfad0, pp=0x7ffee8adfad8 *pp=0x107121050
p=0x7ffee8adfad0, pp=0x7ffee8adfad8 *pp=0x107121054
-4
记住订阅运算符的定义[]
,例如this 在线 C 标准草案中定义:
6.5.2.1 Array subscripting
2) ... The definition of the subscript operator [] is that E1[E2] is
identical to (*((E1)+(E2))). ...
它说 E1[E2] 等同于 (*((E1)+(E2))。然后很明显 (++**pp)[a]
与 *((++**pp)+(a))
相同,这又是与*((a)+(++**pp))
相同,因此读作a[(++**pp)]
。那么++**pp
的值为3
,而a[3]
为-4
。
当您使用数组名称时(在大多数情况下),它会衰减为指向其第一个元素的指针。这意味着 int* p = a;
和 int* p = &a[0];
完全相同。
所以要了解在这种情况下会发生什么,只需一步一步来。在您第一次 printf
调用时,情况如下所示:
pp p a
+-------+ +------+ +----+----+----+----+
| +---------> +--------> -1 | -2 | -3 | -4 |
+-------+ | | +----+----+----+----+
| |
+------+ b
| | +----+----+----+----+
| +---------> 0 | 1 | 2 | 3 |
| | +----+----+----+----+
+------+
pp
指向p
的第一个元素,是指向a
的第一个元素的指针。
现在,当您递增 pp
时,它变为指向 p
的第二个元素,它是指向 b
的第一个元素的指针:
pp p a
+-------+ +------+ +----+----+----+----+
| + | | +--------> -1 | -2 | -3 | -4 |
+---|---+ | | +----+----+----+----+
| | |
| +------+ b
| | | +----+----+----+----+
+---------> +---------> 0 | 1 | 2 | 3 |
| | +----+----+----+----+
+------+
然后您递增 *pp
。由于 *pp
是指向 b
的第一个元素的指针,因此该指针递增以指向 b
的第二个元素:
pp p a
+-------+ +------+ +----+----+----+----+
| + | | +--------> -1 | -2 | -3 | -4 |
+---|---+ | | +----+----+----+----+
| | |
| +------+ b
| | | +----+----+----+----+
+---------> | | 0 | 1 | 2 | 3 |
| + | +----+-^--+----+----+
+---|--+ |
+---------------+
然后你递增**pp
。此时pp
是指向p
的第二个元素的指针,所以*pp
是指向b
的第二个元素的指针。这意味着 **pp
命名了 b
的第二个元素。您将其从 1
增加到 2
:
pp p a
+-------+ +------+ +----+----+----+----+
| + | | +--------> -1 | -2 | -3 | -4 |
+---|---+ | | +----+----+----+----+
| | |
| +------+ b
| | | +----+----+----+----+
+---------> | | 0 | 2 | 2 | 3 |
| + | +----+-^--+----+----+
+---|--+ |
+---------------+
现在,让我们剖析一下(++**pp)[a]
。 ++**pp
与之前相同,因此 b
的第二个元素递增为 3
.
现在,对于任何指针 ptr
和整数 n
,ptr[n]
与 *(ptr + n)
相同。由于加法是可交换的,因此 ptr + n
与 n + ptr
相同。这意味着 ptr[n]
与 n[ptr]
.
相同
把这些放在一起,就是说(++**pp)[a]
和3[a]
一样,也就是a[3]
一样。 a[3]
是 -4
,因此你的结果。
如果将表达式中的所有数组名称表示为它们的衰减值,则最容易理解这一点。 arrayName
作为指针变为 &arrayName[0]
。所以在所有的初始化之后,你有:
a[0] = -1, a[1] = -2, a[2] = -3, a[3] = -4
b[0] = 0, b[1] = 1, b[2] = 2, b[3] = 3
p[0] = &a[0], p[1] = &b[0]
pp = &p[0]
递增指针使其指向下一个数组元素,因此在 ++pp
之后我们现在有
pp = &p[1]
++*pp
取消引用 pp
,所以它等同于 ++p[1]
,所以现在我们有
p[1] = &b[1]
++**pp
两次取消引用,所以它等同于 ++b[1]
,所以现在我们有
b[1] = 2
最后,我们有一个真正令人困惑的表达式 (++**pp)[a]
。 ++**pp
再次递增 b[1]
,所以它的值现在是 3
,并且那个值替换了那个表达式,所以它等同于 3[a]
。这可能看起来像胡说八道(3
不是数组,你怎么能索引它?),但事实证明在 C 中,x[y] == y[x]
因为索引是根据指针算术定义的.所以3[a]
和a[3]
一样,最后一行打印-4
.
你能解释一下输出是如何 -4
的吗?我认为 ++pp;
是 UB 但不确定。您的解释将真正有助于理解。 big-endian 或 little-endian 机器的输出有什么不同吗?
#include <stdio.h>
int a[] = { -1, -2, -3, -4 };
int b[] = { 0, 1, 2, 3 };
int main(void)
{
int *p[] = { a, b };
int **pp = p;
printf("a=%p, b=%p, p=%p, pp=%p\n", (void*)a, (void*)b, (void*)p, (void*)pp);
++pp;
printf("p=%p, pp=%p *pp=%p\n", (void*)p, (void*)pp, (void*)*pp);
++*pp;
printf("p=%p, pp=%p *pp=%p\n", (void*)p, (void*)pp, (void*)*pp);
++**pp;
printf("%d\n", (++**pp)[a]);
}
我的输出:
a=0x107121040, b=0x107121050, p=0x7ffee8adfad0, pp=0x7ffee8adfad0
p=0x7ffee8adfad0, pp=0x7ffee8adfad8 *pp=0x107121050
p=0x7ffee8adfad0, pp=0x7ffee8adfad8 *pp=0x107121054
-4
记住订阅运算符的定义[]
,例如this 在线 C 标准草案中定义:
6.5.2.1 Array subscripting
2) ... The definition of the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2))). ...
它说 E1[E2] 等同于 (*((E1)+(E2))。然后很明显 (++**pp)[a]
与 *((++**pp)+(a))
相同,这又是与*((a)+(++**pp))
相同,因此读作a[(++**pp)]
。那么++**pp
的值为3
,而a[3]
为-4
。
当您使用数组名称时(在大多数情况下),它会衰减为指向其第一个元素的指针。这意味着 int* p = a;
和 int* p = &a[0];
完全相同。
所以要了解在这种情况下会发生什么,只需一步一步来。在您第一次 printf
调用时,情况如下所示:
pp p a
+-------+ +------+ +----+----+----+----+
| +---------> +--------> -1 | -2 | -3 | -4 |
+-------+ | | +----+----+----+----+
| |
+------+ b
| | +----+----+----+----+
| +---------> 0 | 1 | 2 | 3 |
| | +----+----+----+----+
+------+
pp
指向p
的第一个元素,是指向a
的第一个元素的指针。
现在,当您递增 pp
时,它变为指向 p
的第二个元素,它是指向 b
的第一个元素的指针:
pp p a
+-------+ +------+ +----+----+----+----+
| + | | +--------> -1 | -2 | -3 | -4 |
+---|---+ | | +----+----+----+----+
| | |
| +------+ b
| | | +----+----+----+----+
+---------> +---------> 0 | 1 | 2 | 3 |
| | +----+----+----+----+
+------+
然后您递增 *pp
。由于 *pp
是指向 b
的第一个元素的指针,因此该指针递增以指向 b
的第二个元素:
pp p a
+-------+ +------+ +----+----+----+----+
| + | | +--------> -1 | -2 | -3 | -4 |
+---|---+ | | +----+----+----+----+
| | |
| +------+ b
| | | +----+----+----+----+
+---------> | | 0 | 1 | 2 | 3 |
| + | +----+-^--+----+----+
+---|--+ |
+---------------+
然后你递增**pp
。此时pp
是指向p
的第二个元素的指针,所以*pp
是指向b
的第二个元素的指针。这意味着 **pp
命名了 b
的第二个元素。您将其从 1
增加到 2
:
pp p a
+-------+ +------+ +----+----+----+----+
| + | | +--------> -1 | -2 | -3 | -4 |
+---|---+ | | +----+----+----+----+
| | |
| +------+ b
| | | +----+----+----+----+
+---------> | | 0 | 2 | 2 | 3 |
| + | +----+-^--+----+----+
+---|--+ |
+---------------+
现在,让我们剖析一下(++**pp)[a]
。 ++**pp
与之前相同,因此 b
的第二个元素递增为 3
.
现在,对于任何指针 ptr
和整数 n
,ptr[n]
与 *(ptr + n)
相同。由于加法是可交换的,因此 ptr + n
与 n + ptr
相同。这意味着 ptr[n]
与 n[ptr]
.
把这些放在一起,就是说(++**pp)[a]
和3[a]
一样,也就是a[3]
一样。 a[3]
是 -4
,因此你的结果。
如果将表达式中的所有数组名称表示为它们的衰减值,则最容易理解这一点。 arrayName
作为指针变为 &arrayName[0]
。所以在所有的初始化之后,你有:
a[0] = -1, a[1] = -2, a[2] = -3, a[3] = -4
b[0] = 0, b[1] = 1, b[2] = 2, b[3] = 3
p[0] = &a[0], p[1] = &b[0]
pp = &p[0]
递增指针使其指向下一个数组元素,因此在 ++pp
之后我们现在有
pp = &p[1]
++*pp
取消引用 pp
,所以它等同于 ++p[1]
,所以现在我们有
p[1] = &b[1]
++**pp
两次取消引用,所以它等同于 ++b[1]
,所以现在我们有
b[1] = 2
最后,我们有一个真正令人困惑的表达式 (++**pp)[a]
。 ++**pp
再次递增 b[1]
,所以它的值现在是 3
,并且那个值替换了那个表达式,所以它等同于 3[a]
。这可能看起来像胡说八道(3
不是数组,你怎么能索引它?),但事实证明在 C 中,x[y] == y[x]
因为索引是根据指针算术定义的.所以3[a]
和a[3]
一样,最后一行打印-4
.