Art*_*tur 1 memory pointers go
我的问题很简单,但我相信它隐藏了 Go 变量初始化的重要特征。
如果我们有两个变量
i := 5
d := &i
Run Code Online (Sandbox Code Playgroud)
我们想打印它们
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)
Run Code Online (Sandbox Code Playgroud)
输出将类似于
The address of i value is 824633835664
The value of d is 824633835664
The address of d is 824633778224
Run Code Online (Sandbox Code Playgroud)
所以它看起来像&i返回其值的地址5。明白了。我们不明白的是那&d还什么?变量的地址i?
那么是不是这样实现的,值有自己的地址,变量(是值内存地址的别名)在内存中也有自己的地址呢?否则&d将返回我们的地址5
Go 在这里非常传统。您可以将系统的整体内存想象成一个容纳较小盒子的大盒子。每个较小的盒子也可以容纳一些东西。
\n声明一个变量:
\nvar i int\nRun Code Online (Sandbox Code Playgroud)\n告诉 Go 系统它应该在某处分配一个小盒子\xe2\x80\x94,你无法控制在哪里,因为这取决于 Go 系统\xe2\x80\x94,它足够大以容纳一些int值。由于我们尚未将任何值放入框中,Go 会预先将其填充为零:
i:\n+-----+\n| 0 |\n+-----+\nRun Code Online (Sandbox Code Playgroud)\n如果我们设置i为5,则会将 5 放入框中。usingi := 5是告诉 Go 编译器的一种简写方式:在某处创建变量i并将 5 放入框中,这样我们就得到:
i:\n+-----+\n| 5 |\n+-----+\nRun Code Online (Sandbox Code Playgroud)\n现在你有了这个设置,并且因为你组合了声明和赋值,编译器很容易避免任何浪费的工作:它不必先设置i为零,然后设置i为 5。(编译器可以很聪明,并且当然,即使您不使用短声明形式,也可以避免浪费精力,但对我们人类来说,不必考虑这一点更好:短声明形式让我们只需将其视为使用它i创建已经到 5 了。)
我们还有:
\nvar d *int\nRun Code Online (Sandbox Code Playgroud)\n它告诉 Go 系统分配一个足够大的盒子来容纳*int值。它可能与值的大小相同int,或者更大或更小。我将把它画得大约两倍大,这可能是也可能不是您系统上的情况:
d:\n+-----------+\n| nil |\n+-----------+\nRun Code Online (Sandbox Code Playgroud)\n如果我们还没有设置d,Go 会用适当的零值填充它,即nil。
如果我们现在设置d为指向i,则会发生变化,d使其指向i:
d: i:\n+-----------+ +-----+\n| *-----------> | 5 |\n+-----------+ +-----+\nRun Code Online (Sandbox Code Playgroud)\n在这里,您曾经d := &i声明d然后也设置 d为&i,所以我们现在有这种情况(没有浪费任何精力d先设置为 nil,然后将 替换nil为 的值&i)。
您的前两次调用fmt.Printf首先传递值&i,然后传递存储在 中的值d,该值也是&i,因此这两个值打印出来相同。(请注意,这%d通常不是打印指针值的好方法,尽管 Go 包规范fmt说它可以工作。对于现代计算机来说,通常指针最好以十六进制打印;%p格式就是这样做的。)
您的最后一次调用传递了值&d。我们还没有为此绘制一个内存盒,但是为了传递该值,Go 必须将其填充到某个盒子中。1 我们现在可以画出:
unnamed:\n+-----------+\n| * |\n+-----|-----+\n |\n d: v i:\n+-----------+ +-----+\n| *-----------> | 5 |\n+-----------+ +-----+\nRun Code Online (Sandbox Code Playgroud)\n因此,该值 &d位于内存中某个盒子中的某个位置,该位置没有名称(d并且i位于确实有名称的位置,我们可以用来谈论它们)。该框内是一个指针值。指针值指向保存的框d。
最后一次调用fmt.Printf将此指针值\xe2\x80\x94 发送到&d存储在未命名框\xe2\x80\x94 中的该值fmt.Printf,以便fmt.Printf可以打印它。这个值当然必须与存储在框中的值不同d,因为d指向i. 所以最终打印出来的值是不一样的。
该程序总体上有一组可能的正确答案。我们不知道打印的三个值是什么,但我们确实知道第一个和第二个值将相同,并且第三个打印值将与前两个值不匹配。如果没有发生这种情况,则您使用的系统尚未正确实现 Go 语言。2
\n1如果有一种不涉及使用内存框的快速传递值的方法,那么 Go 就可以这样做,只要你在正常语言规则内无法判断 Go 正在这样做。但这同样适用于普通变量!
\nGo 编译器和运行时系统只需产生正确的答案,无论它应用了多少编译时魔法来产生正确的答案。此外,“正确答案”仅针对可能程序的某些子集定义。
\n2这可能有点夸大其辞。我不确定 Go 是否禁止压缩垃圾收集器在内存中移动对象,更新所有指针。实际现有的 Go 系统没有压缩收集器,但人们可以通过使用这样的代码来判断这一点,这至少有点可疑。如果 Go这样做允许压缩收集器,则指针的打印值可能会发生变化,因此打印的前两个值可能会有所不同。
\n| 归档时间: |
|
| 查看次数: |
1859 次 |
| 最近记录: |