首先,快速总结 - 给定声明
int i, *p;
和声明
p = &i;
那么以下是正确的:
p == &i // int * == int *
*p == i // int == int
您已将i 的地址分配给p。因此,表达式 *p 和 i 的计算结果相同。为*p 赋值与为i 赋值相同,从*p 中读取值与从i 中读取值相同。如果你后来做了类似的事情
*p = 10;
那么这就和写一样了
i = 10;
IOW,您正在为p 指向的事物分配一个新值。
如果我们引入多级间接,例如:
int i;
int *p = &i;
int **q = &p;
那么以下都是正确的:
q == &p // int ** == int **
*q == p == &i // int * == int * == int *
**q == *p == i // int == int == int
因此,写入**q 与写入*p 与写入i 相同。写信至*q 与写信至p 相同。
以下内容可能有帮助,也可能没有帮助。
我编写了一个小实用程序来显示内存中各种项目的内容,我将用它来说明p = &i 和*p = i 之间的区别。
首先,这是测试程序:
#include <stdio.h>
#include "dumper.h"
int main( void )
{
int i = 0, j = 0, *p = NULL;
char *names[] = { "i", "j", "p", "*p"};
void *addrs[] = { &i, &j, &p, NULL };
size_t sizes[] = { sizeof i, sizeof j, sizeof p, sizeof *p };
puts( "Before any assignments: ");
dumper( names, addrs, sizes, 3, stdout );
p = &i;
addrs[3] = p;
puts( "After p = &i: " );
dumper( names, addrs, sizes, 4, stdout );
*p = 42;
puts( "After *p = 42: " );
dumper( names, addrs, sizes, 4, stdout );
p = &j;
addrs[3] = p;
puts( "After p = &j: " );
dumper( names, addrs, sizes, 4, stdout );
*p = 10;
puts( "After *p = 10: " );
dumper( names, addrs, sizes, 4, stdout );
return 0;
}
dumper函数显示当时内存中各种对象的状态。
所以,我们从声明开始:
int i = 0, j = 0, *p = 0;
i 和 j 是常规的 ints,p 是指向int 的指针。这意味着存储在p 中的值是其他int 对象的地址。以下是此时内存中的情况:
Item Address 00 01 02 03
---- ------- -- -- -- --
i 0x7ffee3d07a28 00 00 00 00 ....
j 0x7ffee3d07a24 00 00 00 00 ....
p 0x7ffee3d07a18 00 00 00 00 ....
0x7ffee3d07a1c 00 00 00 00 ....
i占用地址0x7ffee3d07a28开始的4个字节1,j占用地址0x7ffee3d07a24开始的四个字节,p占用八个从地址0x7ffee3d07a18 开始的字节。所有三个对象当前都存储 0 值。
接下来,我们执行语句
p = &i;
这会将i 的地址 存储到p。以下是事后的样子:
Item Address 00 01 02 03
---- ------- -- -- -- --
i 0x7ffee3d07a28 00 00 00 00 ....
j 0x7ffee3d07a24 00 00 00 00 ....
p 0x7ffee3d07a18 28 7a d0 e3 (z..
0x7ffee3d07a1c fe 7f 00 00 ....
*p 0x7ffee3d07a28 00 00 00 00 ....
p 现在存储i2 的地址,而不是存储全零。请注意,表达式 *p 与对象 i3 具有相同的有效地址。
现在我们执行语句
*p = 42;
我们的记忆现在是这样的:
Item Address 00 01 02 03
---- ------- -- -- -- --
i 0x7ffee3d07a28 2a 00 00 00 *...
j 0x7ffee3d07a24 00 00 00 00 ....
p 0x7ffee3d07a18 28 7a d0 e3 (z..
0x7ffee3d07a1c fe 7f 00 00 ....
*p 0x7ffee3d07a28 2a 00 00 00 *...
i 的最低有效字节现在存储值0x2a,即十六进制的42。请注意,*p 显示相同的内容。同样,在大多数情况下,*p 等同于 i。
现在,我们将j的地址分配给p:
p = &j;
这是现在的世界状况:
Item Address 00 01 02 03
---- ------- -- -- -- --
i 0x7ffee3d07a28 2a 00 00 00 *...
j 0x7ffee3d07a24 00 00 00 00 ....
p 0x7ffee3d07a18 24 7a d0 e3 $z..
0x7ffee3d07a1c fe 7f 00 00 ....
*p 0x7ffee3d07a24 00 00 00 00 ....
p 现在存储j 的地址,*p 现在等同于j。最后我们将10 分配给*p:
*p = 10;
这给我们留下了
Item Address 00 01 02 03
---- ------- -- -- -- --
i 0x7ffee3d07a28 2a 00 00 00 *...
j 0x7ffee3d07a24 0a 00 00 00 ....
p 0x7ffee3d07a18 24 7a d0 e3 $z..
0x7ffee3d07a1c fe 7f 00 00 ....
*p 0x7ffee3d07a24 0a 00 00 00 ....
j 现在存储值0x0a,这是10 的十六进制值。同样,表达式 *p 等价于j。
- 在大多数系统上,地址会随着运行而变化,因此不要过于关注确切的地址值。
- x86 是 little-endian,因此 least 有效字节是寻址字节。这意味着值读取“向后” - 从左到右,从下到上。
- 这是一个有点戏剧性的许可 - 表达式没有这样的地址。这只是为了说明表达式
*p 在大多数情况下实际上与i 相同。