Go语言中临时变量的地址?

59

如何以最简洁的方式处理这种情况:

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

b *string = &a()

这会生成错误:

无法获取a()的地址

我的理解是,如果取一个局部变量的地址,Go会自动将其提升到堆上。在这里,很明显需要获取返回值的地址。有什么惯用的方法来处理这个问题呢?


你希望通过提出的构造实现什么目标? - Wolf
9个回答

59

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

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

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


3
a()返回一个*string - thwd
7
您可以获取复合字面量的地址,这种字面量没有“归属”的对象。 - newacct
1
复合字面量不一定有一个“家”,但规范保证,通过&运算符获取地址的复合字面量有一个“家”(无论如何都没有其他选择,否则就没有地址可取)。 - zzzz
9
所有这些有关家和无家可归的胡言乱语。除了rvalue和lvalue之外,是否有更直观的命名方法? - Matt Joiner
我为此制作了一个小包,因为如果要使用许多指针属性填充结构,则最终会经常这样做:https://github.com/byrnedo/apibase/blob/master/helpers/pointerhelp/pointerhelp.go - byrnedo

16
请参考Go语言规范相关部分。 &仅适用于以下情况:
  1. 可寻址的东西:变量、指针间接引用、切片索引操作、可寻址结构体的字段选择器、可寻址数组的数组索引操作;或者
  2. 复合字面量
您所拥有的对象都不属于以上情况,因此不能使用&
即使您可以这样做,我也不确定它的含义是什么。获取函数调用结果的地址?通常,您将某物的指针传递给某人,是因为您希望他们能够对所指向的内容进行赋值,并在原始变量中看到更改。但是函数调用的结果是临时的;除非您首先将其分配给某个变量,否则其他人都不会“看到”它。
如果创建指针的目的是创建具有动态生命周期的东西,类似于new()或获取复合字面量的地址,则可以将函数调用的结果分配给一个变量,然后取该变量的地址。

1
如果一个函数返回一个非指针类型的值,那么我认为很明显它默认返回值存储在栈上。但是,如果您立即获取返回值的地址,我不认为会有任何模棱两可的促使将返回存储在堆中的存储,就像正常情况下一样。实际上,由于调用者帧尚未有机会使用返回值,因此调用者可以轻松地在返回时直接将结果复制到堆中。 - Matt Joiner

5
最终您建议Go应该允许您获取任何表达式的地址,例如:
i,j := 1,2
var p *int = &(i+j)
println(*p)

当前的 Go 编译器会输出错误信息:无法获取 i + j 的地址

我认为,允许程序员获取任何表达式的地址:

  • 看起来并不是很有用(也就是说,在真实的 Go 程序中出现的概率非常小)。
  • 这将会使编译器和语言规范更加复杂。

为了获得微小的收益而使编译器和规范变得复杂似乎是适得其反的。


1
嗯,这其实很有道理。我忽略了函数调用是一个表达式这个事实。 - Matt Joiner
错误信息似乎很清楚地说明了Go如何解释&(i+j),但也许并非如此。对于程序员来说,将其解释为“表达式(i+j)执行后产生的值的地址”是合理的(也许在Go中不是这样),有些人确实是这样解释的。(我对Go是否正确或错误没有意见...但'a'是一个表达式吗?&a适用于它,'&(a)'也适用 :-)那么,Go是反对表达式还是堆栈分配的值(并简单地称之为“i+j”)?这重要吗? - hutch
1
@hutch,Go语言(至少在当前实现中)对该表达式表示反感。从程序员的角度来看,编译器如何处理并不重要。 - user811773

1
当你将结果赋值给一个新变量时,不能直接获取其引用,但是你可以通过惯用的方式,在不使用临时变量(它是无用的)的情况下完成此操作,只需预先声明你的“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


1

当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"))

1

最近我也遇到了类似的问题。

首先,在你的例子中谈论字符串是一个干扰,应该使用结构体进行重写,例如:

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()

复制通常不会花费太多,但这些结构可能很大。更糟糕的是,当你想修改结构并使其保持一致时,你不能复制然后修改副本。

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


0

a() 不指向堆栈上的变量。你不能指向堆栈(为什么要这样做?)。

如果你想的话,可以这样做

va := a()
b := &va

但是你真正想要实现的目标有些不清楚。

不,现在我相信va将被存储在堆上。 - Matt Joiner
有人能确认一下在给出的示例代码中,由于以下行中的地址运算符,va将存储在堆上吗? - Matt Joiner
真正的问题似乎是“如果声明块被垃圾回收但b仍然存在,va的值是否会被清除?”。不是吗?我认为由于b中的指针,该值不会被垃圾回收。 - Denys Séguret
一种含蓄的说法是“是的,除非不需要”。 - Matt Joiner

0
在撰写本文时,没有任何答案真正解释为什么会出现这种情况的原因。
请考虑以下内容:

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)
}

如果这段代码片段实际编译了,v会指向什么?!它是一个悬空指针,因为底层对象已被删除。
鉴于此,禁止寻址临时变量似乎是一个合理的限制。

-1

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

临时对象和rvalue

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

来自Primer Plus的内容

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

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

最后但并非最不重要的一点,在go中有一个例外,您可以获取组合文字的地址。天哪,这太混乱了。


我认为用C++示例来展示并不有益,因为C++和Golang是两种完全不同的编程语言,具有完全不同的语法规则。 - Sebi2020
好的,它们共享许多概念...谷歌设计Go语言的动机是因为C++使用起来相当困难...由于谷歌大量使用C++,因此Go语言的设计也受到了C++语法的影响。具有讽刺意味的是,谷歌在很少的内部场景中使用Go语言。 - Izana

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接