使用指针的正确方法是什么。它们是如何工作的?

What's the right way to use pointers. And how do they really work?

我想弄清楚这个问题已经有一段时间了。

据我了解,指针声明如下:

int x =1;
int* p;

据我了解,p 目前没有指向任何东西,但是 p 指向的任何东西都应该是一个 int。有道理。

然后将x的地址传给p,使其指向它的值

p = &x

现在 x 等于什么也等于 *p 反之亦然。

按照这一系列逻辑,如果您想初始化一个指针,您可以这样做:

int x = 1;
int* p = x;

但这让我很困惑。现在我的理解是 p 的值(无论它指向什么)是 x 的值 1,但实际上并不指向 x。那它是如何工作的呢? p 没有指向任何东西,但它指向的是 1?没看懂。

我想知道的另一件事是 pointer = pointer 是如何工作的?

让我们看另一个例子以及我的逻辑:

int x = 1;
int* p;
int* p2;
p = &x;
*p2 = *p;

这对我来说是:p2 的指向值等于指向 p 的值,但不指向 p 指向的变量(即 x),只是指向 x 的值。

有人可以为我澄清一下吗?我的逻辑错误吗?如果是的话我应该怎么看?

之后

int x = 1;

要获得指向 x 的指针,您会说

int * p = &x;

而不是

int * p = x;

所以你认为后者令人困惑是对的。

在你的第二个例子中,当你说

*p2 = *p;

将获取 p 指向的东西(即值 1,包含在变量 x 中)并尝试将其存储在 p2 指向的任何地方.不幸的是 p2 还没有被初始化,也没有指向任何有用的地方,所以结果不太可能是你想要的任何东西。因此,再次重申,如果您对这样做的效果感到困惑,那么您的困惑是对的。

我认为记住指针本身就是整数会有所帮助。所以当你创建一个指针时,它实际上只是内存中的一个偏移量。指针的类型只是告诉编译器在取消引用指针时如何解释数据。所以当你做这样的事情时:

int *p = &x;

您只是将一个整数分配给 p,即 x 的内存地址(偏移量)。从某种意义上说,所有指针实际上都具有相同的 "type",它们是 32 位或 64 位整数,具体取决于体系结构。取消引用这样的指针:

int y = *p;

就是"offset into memory by p bytes, and give me the value there in the form of an int (which is 4 bytes)"。指针的类型只是告诉编译器如何从内存中读取偏移后的数据。

重要的是在计算机内存中没有整数或指针 - 只有字节序列。是您的程序将这些字节解释为 intint*在你的程序之外它只是字节

让我们逐步查看您的示例。假设出于演示目的,您有 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 returns 读取 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|

然而在现实世界中使用未初始化的值经常会导致崩溃。当虚拟内存涉及地址space(我们的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 中(回想一下单元格 #6 已分配给 p2 在第 4 步)

|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|

抱歉这么长的解释,我试图涵盖值、变量、指针和数组之间的关系以及这些的大多数用例。