使用指针的正确方法是什么。它们是如何工作的?
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)"。指针的类型只是告诉编译器如何从内存中读取偏移后的数据。
重要的是在计算机内存中没有整数或指针 - 只有字节序列。是您的程序将这些字节解释为 int
或 int*
。 在你的程序之外它只是字节
让我们逐步查看您的示例。假设出于演示目的,您有 256 字节的内存,而您的 int
s 只有 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|
抱歉这么长的解释,我试图涵盖值、变量、指针和数组之间的关系以及这些的大多数用例。
我想弄清楚这个问题已经有一段时间了。
据我了解,指针声明如下:
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)"。指针的类型只是告诉编译器如何从内存中读取偏移后的数据。
重要的是在计算机内存中没有整数或指针 - 只有字节序列。是您的程序将这些字节解释为 int
或 int*
。 在你的程序之外它只是字节
让我们逐步查看您的示例。假设出于演示目的,您有 256 字节的内存,而您的 int
s 只有 1 字节:
第 1 步: 您在哪里声明和分配变量
program:
int x = 1;
...
假设您的编译器决定为变量 x
:
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
:
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|
抱歉这么长的解释,我试图涵盖值、变量、指针和数组之间的关系以及这些的大多数用例。