【问题标题】:What's the right way to use pointers. And how do they really work? [closed]使用指针的正确方法是什么。它们是如何工作的? [关闭]
【发布时间】:2016-04-22 15:44:24
【问题描述】:

我一直在尝试解决这个问题。

据我了解,指针是这样声明的:

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?我不明白。

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

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

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

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

有人可以为我澄清一下吗?我的逻辑顺序错了吗?如果是这样,我应该如何看待它?

【问题讨论】:

  • 我想我们无法在几段中回答这样的问题。花数周时间阅读有关编程、C 语言、操作系统、计算机体系结构、指令集等方面的书籍。
  • int* p = x; 不起作用。它应该是 int* p = &x; 。一般规则 - 左侧事物的“类型”应与右侧事物的“类型”相同。这适用于所有赋值和初始化,不仅是那些涉及指针的赋值和初始化
  • int *p; 是一个指向整数的指针。也就是说,p 保存一个整数的地址。当您执行int *p = x; 时,您将x 的值分配为整数的地址,这可能无效(如果x 的值是1,那么您是在说整数的地址是 1,这可能是无效的)。这应该是int *p = &x;,这意味着您将p 的值分配为变量x地址
  • 也许this 会有所帮助?
  • comp.lang.c FAQ,第 4 节。

标签: c pointers memory-address


【解决方案1】:

之后

int x = 1;

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

int * p = &x;

而不是

int * p = x;

所以你发现后者令人困惑是对的。

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

*p2 = *p;

这将把p 指向的东西(这是1 的值,包含在变量x 中)并尝试将它存储在p2 指向的任何地方。不幸的是,p2 还没有被初始化,也没有指向任何有用的地方,所以结果不太可能是你想要的。所以,再一次,如果你对那会做什么感到困惑,那么你感到困惑是正确的。

【讨论】:

  • 但是int * p = &x; 不是说p 指向的是x 的地址吗?就像我打印指针值一样,它不会打印 x 的地址而不是它的值吗?
  • int * p = &x时,int *部分就是p的类型;这并不意味着此时正在跟踪指针p。 (不过以后写*p = 3;,确实是这个意思,习惯就好了……)
  • @GarethMcCaughan 所以“地址值”运算符“*”在初始化时并不意味着相同?
  • int * p = &x; 中,* 根本不是运算符,它是 type 的一部分,因此与它的含义不同当它是一个运算符时会这样做。
  • @GarethMcCaughan 这 100% 是我的问题。非常感谢。
【解决方案2】:

重要的是,在计算机内存中没有整数或指针 - 只有字节序列。是您的程序将这些字节解释为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 读取返回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|

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

【讨论】:

    【解决方案3】:

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

    int *p = &x;
    

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

    int y = *p;
    

    只是意味着“在内存中偏移 p 个字节,并以 int 的形式(即 4 个字节)给我那里的值”。指针的类型只是告诉编译器如何从内存中读取偏移后的数据。

    【讨论】:

    • 考虑到我过去做过内存操作,这确实有帮助,谢谢。
    • 对此有一个非常重要的警告:C 允许您进行涉及指针的算术运算,为此,如果您将它们视为表示机器地址的整数,您有时会得到错误的答案。为什么?因为例如如果p 是指向int 的指针,那么p+1 表示“一个int 在内存中比p 更远”,这在典型机器上实际上意味着将4 添加到地址,因为ints通常大小为 4 个字节。 (虽然 2 字节和 8 字节并不是闻所未闻。)
    • 类似地,如果pq 是指向int 的指针(严格来说,它们最好也位于单个数组或malloced 内存块中)然后@ 987654334@ 以int 的大小为单位,而不是以字节为单位。所以例如如果你说int x[10]; p=&x[1]; q=&x[3]; 那么p 可能是0x23006C,q 可能是0x230074, and q-p` 将是2(而不是8)。
    • 我认为它实际上是指针的大小,如果它是一个指向字符的指针,p+1 将只是 +1,因为字符是一个字节长。好吧,更具体地说,每当您更改地址时,它都是sizeof(type)
    猜你喜欢
    • 2019-03-22
    • 1970-01-01
    • 2014-06-29
    • 1970-01-01
    • 2023-04-09
    • 1970-01-01
    • 2010-09-18
    • 2012-04-15
    • 1970-01-01
    相关资源
    最近更新 更多