p 是指向 int 或某个 ints 的指针。
您将其初始化为指向地址 17。(这是一个大问题,有几个原因,我们会谈到。)
然后您将 4 添加到它。显然,您机器上的sizeof(int) 是4,也就是说,int 占用4 个字节(32 位)。当您将 4 添加到 int 指针时,编译器知道您想让它指向 4 int 的值更远,因此编译器将 4 × 4 = 16 添加到地址。现在p 指向地址 33。
然后你“转换”(转换)p 从指针到long。所以现在,不是指向地址 33 的指针,而是数字 33。
然后你再次施放它,从long 到int。这基本上将其从 33 转换为 33。(如果您机器上的类型 long 大于 4 个字节,则此转换可能涉及从 64 位值 33 转换为 32 位值 33,即,从0x0000000000000021 到0x00000021。)
然后使用%d 打印最后一个int 值。所以你看到了数字 33。
现在,通常是这样说的
int* p = (int*)17;
是个坏主意,因为您有一个指针,其值可能无法使用。通常,您对指针所做的一件事是操作它们指向的值。但是如果你说
printf("value pointed to by p is %d\n", *p);
您最终会尝试从内存中的地址 17 获取 int 值。但是 (a) 您可能没有从地址 17 读取的权限,并且 (b) 17 不是 4 的倍数,因此您的处理器可能甚至不愿意尝试从那里获取 int,即使您确实得到了许可。所以这段代码几乎肯定会崩溃。
但是由于在您的代码中,您实际上从未尝试对 p 假设指向的 int 执行任何操作(在您添加 4 之前或之后都没有),您的代码可能 - 并且几乎没有 - - 似乎“工作”。
一方面,这是糟糕的、不可移植的、几乎没有定义的代码,如果不是完全未定义的话。但是话又说回来,它可能并不实用(显然没有人会运行它来完成任何实际工作)。因此,如果我们从中学到的只是指针运算的工作原理(特别是编译器如何根据指向对象的大小自动缩放),也许我们不必花费太多时间痛斥它的许多丑陋和不完美之处。 (关于代码是 undefined 还是仅仅是 implementation-defined,评论线程中有一些小问题,但只要你注意不要编写代码像这样真的,你不必担心区别。)
如果您想学习或多或少相同的关于指针运算的课程,但使用更合理且大多数可移植的程序,试试这个:
#include <stdio.h>
int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int main()
{
int *p = &a[3];
printf("p is %p and points to %d\n", p, *p);
printf("p+4 is %p and points to %d\n", p+4, *(p+4));
}
在这里,我们没有将 17 之类的人工值填充到 p 中,而是将其初始化为指向一个实际的数组,正如指针所指的那样。而不是将指针值转换为int 并使用%d 打印它,而是使用%p 打印它,它旨在打印指针。
在我的机器上,这个程序打印
p is 0x10f47a02c and points to 3
p+4 is 0x10f47a03c and points to 7
如您所见,指针值不是像 17 和 33 这样容易理解的小数字,而我的机器选择以十六进制打印它们。尽管如此,很容易验证0x10f47a03c - 0x10f47a02c 是0x10,或16。我们添加了4,意思是“让它指向现在指向的第4 个int”,编译器添加了16。
[脚注。我说这是“主要是便携式的”。为了使其完美便携,您必须将 printf 调用更改为
printf("p is %p and points to %d\n", (void *)p, *p);
printf("p+4 is %p and points to %d\n", (void *)(p+4), *(p+4));
严格来说,%p 仅定义为打印指向void 的通用指针,而不是任意其他指针类型。所以,严格来说,我们需要那些 (void *) 强制转换,将指针值转换为正确的指针类型以便打印。]