重要的是,在计算机内存中没有整数或指针 - 只有字节序列。是您的程序将这些字节解释为int 或int*。 在你的程序之外它只是字节
让我们一步一步看你的例子。假设出于演示目的,您有 256 个字节的内存,而您的 ints 只有 1 个字节:
第 1 步:
你在哪里声明和分配一个变量
program:
int x = 1;
...
假设您的编译器决定为变量 x 分配内存单元 12:
memory |address:value| (value of X means unknown):
|0:X|1:X|2:X| ... |12:1| ... |255:X|
第 2 步:
你在哪里声明指针
program:
int x = 1;
int *p;
...
调用第 1 步 ...并且编译器为变量 p 分配了内存单元 5(注意 - p 尚未初始化,因此单元 5 中的值是 X!) :
memory |address:value| (value of X means unknown):
|0:X|1:X|2:X| ... |5:X| .. |12:1| ... |255:X|
第 3 步:
你在哪里分配指针
program:
int x = 1;
int *p;
p = &x;
...
调用第 2 步 ...现在单元格 p 存储指向 x 的指针。我们知道单元格 12 已分配给 x,因此 12 存储到 p(单元格 5)中:
memory |address:value| (value of X means unknown):
|0:X|1:X|2:X| ... |5:12| .. |12:1| ... |255:X|
第 4 步:
你在哪里声明第二个指针
program:
int x = 1;
int *p;
p = &x;
int *p2;
...
调用第 3 步 ...并且您的编译器为变量 p2 分配了内存单元 6(它尚未分配,因此值为 X):
memory |address:value| (value of X means unknown):
|0:X|1:X|2:X| ... |5:12|6:X| .. |12:1| ... |255:X|
第 5 步:
你在哪里分配第二个(仍然未定义!)pointee:
program:
int x = 1;
int *p;
p = &x;
int *p2;
*p2 = *p;
...
调用第 4 步,然后 *p 使编译器想起 p 是单元格 5,因此它会生成从单元格 5 读取的指令(值 12)并将该值用作地址实际结果:从单元格12 读取返回1。现在编译器知道名称p2 已赋予单元格#6 p2 为6,但*p2 意味着您需要读取p2 的值并使用它作为实际存储的地址。但在我们的例子中,这个值是X(未定义)!然而,在实际的内存芯片中,没有未定义的值,并且在启动时已经存储了一些东西(比如值42)。如果虚拟内存受到影响,事情会变得更加复杂,但我不想在这里讨论。因此,假设在启动时电荷和宇宙射线将此单元初始化为随机值42。让我们看看当*p2 = *p 被执行时会发生什么:我们已经看到*p 如何返回1,现在程序将尝试找到将其存储到的位置。它将读取p2 的值,产生42,现在程序会将1 存储到内存单元42,结果如下:
memory |address:value| (value of X means unknown):
|0:X|1:X|2:X| ... |5:12|6:X| .. |12:1| ... |42:1| ... |255:X|
在现实世界中,但是使用未初始化的值经常会导致崩溃。当虚拟内存涉及地址空间的某些区域(我们的 256 个地址)时,可能没有为它们分配内存单元,写入这样的地址会导致崩溃。
第 6 步:
您在其中分配第二个指针并使其再次定义指向x:
program:
int x = 1;
int *p;
p = &x;
int *p2;
p2 = p;
...
调用第 4 步并忽略导致程序崩溃的第 5 步 ...现在编译器知道哪些单元格用于 p(单元格 5)和 p2(单元格 6)。因此赋值p2=p 以这种方式工作:从p(即12)中获取值,不要将其解释为指针,而只需将其复制到p2。现在p2 被初始化为指向x(单元格12)的指针。
memory |address:value| (value of X means unknown):
|0:X|1:X|2:X| ... |5:12|6:12| .. |12:1| ... |42:X| ... |255:X|
第 7 步:
您将指针分配到未命名位置的位置:
program:
int x = 1;
int *p;
p = &x;
int *p2;
p2 = (int*)100;
...
...让我们关注这一行:p2 = (int*)100; 这显示了如何分配指针以指向未命名的内存单元。回想一下,编译器尚未为单元格#100 分配名称。但是如果你确定在这个内存位置有一些有趣的东西,例如一个计算机设备正在通过那个单元与我们通信,那该怎么办。如果键盘将下一个字符存储到存储单元#100 并且我们想读取它怎么办?现在你可以把它读成*p2:
memory |address:value| (value of X means unknown):
|0:X|1:X|2:X| ... |5:12|6:100| .. |12:1| ... |42:X| ... |100:keyboard| ... |255:X|
第 8 步:
您正在揭示指向数组的指针的关系:
program:
int x = 1;
int *p;
p = &x;
int a[5];
a[2]=18;
int *p2 = a;
p2[2]=3;
...
...让我们关注这一行:int a[5];:此声明行告诉编译器分配 5 个单元并为它们使用名称 a。并且编译器为a分配了单元格7、8、9、10、11:
memory |address:value| (value of X means unknown):
|0:X|1:X|2:X| ... |5:12|6:100|7:X|8:X|9:X|10:X|11:X|12:1| ... |42:X| ... |100:keyboard| ... |255:X|
请注意,它没有分配指针——a 将像任何其他变量一样使用!这就是a[2]=18 的工作原理:第一个编译器知道a 开始和7,因此它使用该值作为开始,然后添加2 以获得9。所以找到了目标存储单元 - 它是单元编号 9,因此它将 18 存储到该单元中:
|0:X|1:X|2:X| ... |5:12|6:100|7:X|8:X|9:18|10:X|11:X|12:1| ... |42:X| ... |100:keyboard| ... |255:X|
让我们看看int *p2 = a; 是如何工作的:编译器知道a 从7 开始。因此7 被存储到p2 中(回想第4 步中单元格#6 已分配给p2)
|0:X|1:X|2:X| ... |5:12|6:7|7:X|8:X|9:18|10:X|11:X|12:1| ... |42:X| ... |100:keyboard| ... |255:X|
初始化指针后,您可以将其用作数组p2[2]=3;:这是*(p2 + 2) = 3 的缩写。 p2 的值为 7,加 2 为 9,因此我们将 3 存储到单元格 #9:
|0:X|1:X|2:X| ... |5:12|6:7|7:X|8:X|9:3|10:X|11:X|12:1| ... |42:X| ... |100:keyboard| ... |255:X|
抱歉,解释了这么长,我试图涵盖值、变量、指针和数组之间的关系以及这些的大多数用例。