【问题标题】:Address of a temporary in Go?Go中的临时地址?
【发布时间】:2012-05-19 03:04:57
【问题描述】:

处理这种情况的最干净的方法是什么:

func a() string {
    /* doesn't matter */
}

b *string = &a()

这会产生错误:

不能取a()的地址

我的理解是,如果一个局部变量的地址被占用,Go 会自动将其提升到堆中。这里很清楚,要取返回值的地址。处理这个问题的惯用方法是什么?

【问题讨论】:

  • 您想通过所讨论的结构实现什么目标?

标签: pointers return go temporary rvalue


【解决方案1】:

地址运算符返回一个指向具有“家”的东西的指针,例如。一个变量。您的代码中表达式的值是“无家可归”。如果你真的需要一个 *string,你必须分两步完成:

tmp := a(); b := &tmp

请注意,虽然 *string 有完全有效的用例,但很多时候使用它们是错误的。在 Go 中,string 是一种值类型,但传递起来很便宜(一个指针和一个 int)。字符串的 是不可变的,更改 *string 会更改“home”指向的位置,而不是字符串值,因此在大多数情况下,根本不需要 *string

【讨论】:

  • 或从a()返回*string
  • 你也可以取一个没有“家”的复合字面量的地址
  • 复合文字不一定有家,但规范保证由& 运算符获取地址的复合文字有一个“家”(无论如何都没有其他选项,否则有将没有地址)。
  • 所有这些家庭和无家可归的胡言乱语。还有比右值和左值更直观的命名法吗?
  • 我为此做了一个小包,因为如果用大量指针属性填充结构,最终会做很多事情:github.com/byrnedo/apibase/blob/master/helpers/pointerhelp/…
【解决方案2】:

请参阅Go language spec 的相关部分。 & 只能用于:

  1. 可寻址的事物:变量、指针间接寻址、切片索引操作、可寻址结构的字段选择器、可寻址数组的数组索引操作;或
  2. 复合文字

你所拥有的都不是那些,所以它不起作用。

即使你能做到,我什至不确定这意味着什么。获取函数调用结果的地址?通常,您将某事物的指针传递给某人,因为您希望他们能够分配给指向的事物,并查看原始变量的变化。但是函数调用的结果是暂时的;除非您先将其分配给某物,否则没有其他人“看到”它。

如果创建指针的目的是创建具有动态生命周期的东西,类似于new() 或获取复合文字的地址,那么您可以将函数调用的结果分配给变量并获取地址那个。

【讨论】:

  • 如果一个函数返回一个非指针值,我似乎很清楚它假定返回值存储在堆栈中。但是,如果您立即获取返回值的地址,我看不出任何促进存储返回到堆上的歧义,正常吗?见鬼,由于调用者框架还没有机会使用返回值,调用者可以很容易地在返回时直接将结果复制到堆中。
【解决方案3】:

最后你建议Go应该允许你获取任何表达式的地址,例如:

i,j := 1,2
var p *int = &(i+j)
println(*p)

当前的 Go 编译器打印错误:cannot take the address of i + j

在我看来,允许程序员取任意表达式的地址:

  • 似乎不是很有用(即:在实际 Go 程序中出现的概率似乎很小)。
  • 这会使编译器和语言规范复杂化。

使编译器和规范复杂化而收效甚微似乎适得其反。

【讨论】:

  • 嗯,这实际上很有意义。我忽略了函数调用是一个表达式这一事实。
  • 错误消息似乎很清楚 Go 如何解释 &(i + j),但可能不是。程序员将其解释为“由执行表达式 (i + j) 产生的值的地址”是合理的(可能不是在 Go 中),而且有些人实际上是这样解释的。 (我对 Go 是对还是错没有意见……但是 'a' 是一个表达式吗?&a 对它有效,'&(a)' 也有效 :-) 那么,Go 是反对表达式还是堆栈分配的值(并简单地用名称“i + j”调用它)?有关系吗?
  • @hutch Go(至少在当前实现中)反对该表达式。从程序员的角度来看,编译器如何做并不重要。
【解决方案4】:

我最近因类似的事情而纠结。

首先在您的示例中谈论字符串会分散注意力,请改用结构,将其重写为:

func a() MyStruct {
    /* doesn't matter */
}

var b *MyStruct = &a()

这不会编译,因为你不能获取 a() 的地址。这样做:

func a() MyStruct {
    /* doesn't matter */
}

tmpA := a()
var b *MyStruct = &tmpA

这将编译,但是您已经在堆栈上返回了一个 MyStruct,在堆上分配了足够的空间来存储一个 MyStruct,然后将内容从堆栈复制到堆。如果你想避免这种情况,那就这样写:

func a2() *MyStruct {
  /* doesn't matter as long as MyStruct is created on the heap (e.g. use 'new') */
}

var a *MyStruct = a2()

复制通常很便宜,但这些结构可能很大。更糟糕的是,当您想修改结构并将其“粘贴”时,您不能先复制然后再修改副本。

无论如何,当您使用返回类型的 interface{} 时,它会变得更加有趣。 interface{} 可以是结构体或指向结构体的指针。出现同样的复制问题。

【讨论】:

    【解决方案5】:

    在分配给新变量时,您无法直接获取结果的引用,但是您可以通过简单地预先声明“b”指针来使用惯用的方法来执行此操作,而无需使用临时变量(它没用) - 这是你错过的真正步骤:

    func a() string {
        return "doesn't matter"
    }
    
    b := new(string) // b is a pointer to a blank string (the "zeroed" value)
    *b = a()         // b is now a pointer to the result of `a()`
    

    *b 用于取消引用指针并直接访问保存数据的内存区域(当然是在堆上)。

    玩代码:https://play.golang.org/p/VDhycPwRjK9

    【讨论】:

      【解决方案6】:

      a() 没有指向堆栈中的变量。你不能指向堆栈(你为什么要?)。

      如果你愿意,你可以这样做

      va := a()
      b := &va
      

      但你真正想要达到的目标有些不清楚。

      【讨论】:

      • 不,现在我相信va 将存储在堆上。
      • 有人可以确认在给出的示例代码中,va 将存储在堆上,因为下一行的地址运算符?
      • 真正的问题似乎是“如果声明块被垃圾但 b 仍然存在,GC 是否会清除 va 的值?”。不是吗?我认为由于 b 中的指针,该值不会被垃圾。
      • 一种关于“是的,除非它不需要”的说法。
      【解决方案7】:

      在撰写本文时,没有任何答案能真正解释为什么会出现这种情况。

      考虑以下几点:

      
      func main() {
          m := map[int]int{}
          val := 1
          m[0] = val
          v := &m[0] // won't compile, but let's assume it does 
          delete(m, 0)
          fmt.Println(v)
      }
      

      如果这段代码 sn-p 实际编译,v 会指向什么!?这是一个悬空指针,因为底层对象已被删除。

      鉴于此,禁止寻址临时对象似乎是一个合理的限制

      【讨论】:

        【解决方案8】:

        是的,当 API 需要使用 *string 输入时,即使您经常希望将文字字符串传递给它们,这也会很烦人。

        为此我做了一个非常小的函数:

        // Return pointer version of string
        func p(s string) *string {
            return &s
        }
        

        然后我没有尝试调用foo("hi") 并得到可怕的cannot use "hi" (type string) as type *string in argument to foo,而是将参数包装在对p() 的调用中:

        foo(p("hi"))
        

        【讨论】:

          【解决方案9】:

          猜你需要更有效的 Cpp 的帮助 ;-)

          临时对象和右值

          真正的临时对象在 C++ 中是不可见的 - 它们不会出现在您的源代码中。每当创建了非堆对象但未命名时,它们就会出现。此类未命名对象通常出现在以下两种情况之一:应用隐式类型转换以使函数调用成功时以及函数返回对象时。”

          Primer Plus

          左值是可以通过用户地址引用的数据对象(命名对象)。非左值包括文字常量(除了引用的字符串,它们由它们的地址表示),具有多个术语的表达式,例如 (a + b)。

          在 Go 语言中,字符串文字将被转换为 StrucType 对象,这将是一个不可寻址的临时结构对象。在这种情况下,Go 中的地址不能引用字符串字面量。

          好吧,最后但并非最不重要的一个例外是,您可以获取复合文字的地址。天哪,真是一团糟。

          【讨论】:

          • 我认为用 C++ 示例来说明这一点并没有帮助,因为 C++ 和 golang 是两种完全不同的编程语言,具有完全不同的语法规则。
          • 嗯,他们分享了很多概念……谷歌设计围棋的动机是因为 C++ 很难使用……由于谷歌大量使用 C++,围棋的设计是也受 C++ 语法的影响。具有讽刺意味的是,谷歌在极少数内部场景中使用 Go。
          猜你喜欢
          • 1970-01-01
          • 2011-03-21
          • 1970-01-01
          • 2018-05-02
          • 1970-01-01
          • 2019-12-22
          • 2013-05-05
          • 2016-05-28
          • 1970-01-01
          相关资源
          最近更新 更多