【问题标题】:Addresses of pointers in GolangGolang 中的指针地址
【发布时间】:2020-09-02 17:52:11
【问题描述】:

我的问题很简单,但我相信它隐藏了 Go 变量初始化的重要特性。

如果我们有两个变量

i := 5
d := &i

我们想打印它们

fmt.Printf("The address of i value is %d\n", &i)
fmt.Printf("The value of d is %d\n", d)
fmt.Printf("The address of d is %d", &d)

输出将类似于

The address of i value is 824633835664
The value of d is 824633835664
The address of d is 824633778224

所以看起来&i 返回其值5 的地址。明白了。 不明白的是&d 还给我们什么?变量i的地址?

那么是否实现得让值有自己的地址,而变量(值内存地址的别名)在内存中也有自己的地址? 否则&d 将返回5 的地址

【问题讨论】:

标签: go pointers memory


【解决方案1】:

Go 在这里非常传统。您可以将系统的整体内存想象成一个容纳小盒子的大盒子。每个小盒子也可以装东西。

声明一个变量:

var i int

告诉 Go 系统它应该在某处分配一个小盒子——你无法控制 where,因为这取决于 Go 系统——它大到足以容纳一些 int 值.由于我们还没有将任何值放入框中,Go 将其预填充为零:

 i:
+-----+
|  0  |
+-----+

如果我们将 i 设置为 5,则将 5 放入框内。使用 i := 5 是告诉 Go 编译器的一种简写方式:在某处创建变量 i 并将 5 放入框中全部放在一行中,这样我们得到:

 i:
+-----+
|  5  |
+-----+

所以你现在有了这个设置,因为你结合了声明和赋值,编译器很容易避免任何浪费的努力:它不必先将i设置为零,然后将i设置为5.(当然,编译器可以很聪明,避免浪费精力,即使你没有使用简短的声明形式,但我们人类不必考虑这一点更好:简短的声明形式让我们可以将其视为创建i,并将其设置为5。)

我们还有:

var d *int

告诉 Go 系统分配一个足够大的盒子来容纳 *int 值。这可能与int 值的大小相同,或者更大或更小。我会把它画出两倍大,你的系统可能是这样,也可能不是:

 d:
+-----------+
|    nil    |
+-----------+

如果我们还没有设置d,Go 会用适当的零值填充它,即nil

如果我们现在将d 设置为指向i,这将更改d,使其指向i

 d:                  i:
+-----------+       +-----+
|     *-----------> |  5  |
+-----------+       +-----+

在这里,您使用d := &i 声明d,然后设置 d&i,所以我们现在有这种情况(没有任何努力浪费在设置@987654347先将 @ 设为 nil,然后将 nil 也替换为 &i 的值。

您对fmt.Printf 的前两次调用传递,首先是值&i,然后是存储在d 中的值,这也是&i,所以这两个值打印出来是一样的。 (请注意,%d 通常不是打印指针值的好方法,尽管 fmt 的 Go 包规范说它会起作用。对于现代计算机,通常最好以十六进制打印指针;%p 格式这样做。)

您的最后一次调用传递了值&d。我们还没有为此绘制一个记忆框,但是为了传递这个值,Go 必须将它塞进某个盒​​子的某个地方。1我们现在可以绘制它了:

 unnamed:
+-----------+
|     *     |
+-----|-----+
      |
 d:   v              i:
+-----------+       +-----+
|     *-----------> |  5  |
+-----------+       +-----+

所以 &d 位于内存中的某个盒子中,位于没有名称的位置(di 位于确实有名称的位置)可以用来谈论它们)。在那个盒子里面是一个指针值。指针值指向包含d 的框。

fmt.Printf 的最后一次调用将这个指针值(这个&d 值,存储在未命名的框中)发送到fmt.Printf,以便fmt.Printf 可以打印它。这个值当然必须不同于存储在d 框中的值,因为d 指向i。所以最终打印出来的值不一样。

这个程序总体上有一组可能的正确答案。我们不知道打印的三个值是什么,但我们确实知道第一个和第二个值将是相同,而第三个打印值将 em>不匹配前两个。如果这没有发生,则您使用的系统没有正确实现 Go 语言。2


1如果有一种快速传递值的方法涉及使用内存盒,Go 可以这样做,只要你不知道,在正常的语言规则内,Go 正在这样做。但同样适用于普通变量!

Go 编译器和运行时系统只需要生成正确答案,不管它应用了多少编译时魔法来产生正确答案。此外,“正确答案”仅针对可能程序的某些子集定义。

2这可能有点夸大了。我不确定 Go 是否禁止压缩垃圾收集器在内存中移动对象,更新所有指针。实际现有的 Go 系统没有压缩收集器,但是可以通过使用这样的代码来判断这一点,这至少有点可疑。如果 Go 允许 允许压缩收集器,则指针的打印值可能会发生变化,因此打印的前两个值可能会不同。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-12-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多