指针变量的地址是如何分配的?他们遵循任何模式吗?

How are the addresses given to pointer variables? Do they follow any pattern?

#include<stdlib.h>

int main()
{
   char ch, *p1, **p2, ***p3, ****p4;
   ch='a';
   p1=&ch;
   printf("%c %d %c\n", ch, p1, *p1);

   p2=&p1;
   printf("%d %d %c\n", p2, *p2, **p2);

   p3=&p2;
   printf("%d %d %d %c\n", p3, *p3, **p3, ***p3);

   p4=&p3;
   printf("%d %d %d %d %c\n", p4, *p4, **p4, ***p4, ****p4);
}

输出如下:

a 298923415 a                                                                                                                 
298923416 298923415 a                                                                                                          
298923424 298923416 298923415 a                                                                                                
298923432 298923424 298923416 298923415 a           

为什么p1和p2分配的地址以1为增量,p3和p4分配的地址以8为增量?

寻址是否遵循任何模式,因为它们被分配到连续的内存位置?

发生什么取决于编译器做什么。语言本身并不能保证,但通常它们会在堆栈中彼此靠近放置。有一个很好的例子说明它如何在 x86-64 上工作(你的电脑可能有)here:

该示例使用以下代码:

long myfunc(long a, long b, long c, long d,
        long e, long f, long g, long h)
{
    long xx = a * b * c * d * e * f * g * h;
    long yy = a + b + c + d + e + f + g + h;
    long zz = utilfunc(xx, yy, xx % yy);
    return zz + 20;
}

与您的问题相关的是 xx yy zz 部分。就像您的问题一样,这些是在堆栈上声明的变量。正如您在图片中看到的,它们并排放置。 xx的地址最高,然后是yy,然后是zz。为了说明这一点,您可以执行 (&xx)[-1] 并且 可能 获得 yy 的值。请注意,这是 未定义的行为 并且仅在我们知道编译器如何处理它时才起作用,在任何不同的设置中它可能不起作用,可能导致错误并使程序崩溃(或更糟)。

此外,请考虑 this post 内存对齐,这可能会导致您的数据之间存在间隙,以使其 运行 更快。

在这种情况下,您的对象在内存中按顺序排列。他们不一定非得如此。编译器可以根据需要在内存中放置内容。

在你的情况下,你的记忆看起来是这样的:

298923415 +-----------+
          |    'a'    | ch
298923416 +-----------+
          |           |
          |           |
          |           |
          | 298923415 | p1
          |           |
          |           |
          |           |
          |           |
298923124 +-----------+
          |           |
          |           |
          |           |
          | 298923416 | p2
          |           |
          |           |
          |           |
          |           |
298923132 +-----------+
          |           |
          |           |
          |           |
          | 298923424 | p3
          |           |
          |           |
          |           |
          |           |
298923140 +-----------+
          |           |
          |           |
          |           |
          | 298923432 | p4
          |           |
          |           |
          |           |
          |           |
          +-----------+

请记住,在大多数现代系统中,指向对象的指针类型只是整数,其作用是存储内存地址。在您的系统上(我猜 x86_64)指针的大小为 8 个字节。不过,这完全取决于系统。例如,一个指针在 32 位 x86 系统上是 4 个字节,还有许多其他平台具有奇异的指针大小。

char 的大小也定义为 1,因此第一个指针位于 ch 之后的一个字节。

您读取输出的方式有误。 ch 分配在地址 298923415。指针 p1 分配在下一个字节 298923416。从那里开始,每个指针按 8 字节的倍数分配,建议使用 64 位系统。

这只是意味着编译器分配 char 与指针不同。这是因为char是1个字节,没有对齐要求,所以可能会放在未对齐的地址。

一般来说,编译器可以随意分配变量到它喜欢的地方,你没有任何保证。如果不考虑特定的系统,讨论堆栈内存布局是没有意义的。

将您的示例修改得更清晰,结果并不引人注目:

#include <stdio.h>

int main (void)
{
   char ch = 'a';
   char*    p1 = &ch;
   char**   p2 = &p1; 
   char***  p3 = &p2;
   char**** p4 = &p3;
   printf("ch:\t%p\n", (void*)p1);
   printf("p1:\t%p\n", (void*)p2);
   printf("p2:\t%p\n", (void*)p3);
   printf("p3:\t%p\n", (void*)p4);
   printf("p4:\t%p\n", (void*)&p4);
}

输出(x64 Windows PC,十六进制地址):

ch:     000000000022FE4F
p1:     000000000022FE40
p2:     000000000022FE38
p3:     000000000022FE30
p4:     000000000022FE28