英文:
Address of a temporary in Go?
问题
这种情况下,处理的最干净的方式是什么?
func a() string {
/* doesn't matter */
}
b *string = &a()
这会生成错误:
无法取地址 of a()
我理解的是,如果取了一个局部变量的地址,Go会自动将其提升到堆上。在这里,明确要取返回值的地址。有什么惯用的方式来处理这个问题?
英文:
What's the cleanest way to handle a case such as this:
func a() string {
/* doesn't matter */
}
b *string = &a()
This generates the error:
> cannot take the address of a()
My understanding is that Go automatically promotes a local variable to the heap if its address is taken. Here it's clear that the address of the return value is to be taken. What's an idiomatic way to handle this?
答案1
得分: 59
地址运算符返回一个指向具有“家”的东西(例如变量)的指针。你代码中表达式的值是“无家可归的”。如果你真的需要一个*string,你需要分两步来做:
tmp := a(); b := &tmp
请注意,虽然*string有完全有效的用例,但很多时候使用它们是错误的。在Go中,string
是一个值类型,但是传递它是很廉价的(一个指针和一个整数)。字符串的值是不可变的,改变*string
会改变“家”的指向,而不是字符串的值,所以在大多数情况下根本不需要使用*string
。
英文:
The address operator returns a pointer to something having a "home", e.g. a variable. The value of the expression in your code is "homeless". if you really need a *string, you'll have to do it in 2 steps:
tmp := a(); b := &tmp
Note that while there are completely valid use cases for *string, many times it's a mistake to use them. In Go string
is a value type, but a cheap one to pass around (a pointer and an int). String's value is immutable, changing a *string
changes where the "home" points to, not the string value, so in most cases *string
is not needed at all.
答案2
得分: 16
请参阅Go语言规范中的相关部分。&
只能用于以下情况:
- 可寻址的东西:变量、指针间接引用、切片索引操作、可寻址结构体的字段选择器、可寻址数组的索引操作;或者
- 复合字面量
你所拥有的不是以上任何一种情况,所以它不起作用。
即使你能够这样做,我甚至不确定它的含义是什么。获取函数调用结果的地址?通常,你将某个东西的指针传递给某人,是因为你希望他们能够对指向的东西进行赋值,并在原始变量中看到更改。但函数调用的结果是临时的;除非你首先将其赋值给某个东西,否则没有其他人“看到”它。
如果创建指针的目的是为了创建具有动态生命周期的东西,类似于new()
或获取复合字面量的地址,那么你可以将函数调用的结果赋值给一个变量,然后取该变量的地址。
英文:
See the relevant section of the Go language spec. &
can only be used on:
- Something that is addressable: variable, pointer indirection, slice indexing operation, field selector of an addressable struct, array indexing operation of an addressable array; OR
- A composite literal
What you have is neither of those, so it doesn't work.
I'm not even sure what it would mean even if you could do it. Taking the address of the result of a function call? Usually, you pass a pointer of something to someone because you want them to be able to assign to the thing pointed to, and see the changes in the original variable. But the result of a function call is temporary; nobody else "sees" it unless you assign it to something first.
If the purpose of creating the pointer is to create something with a dynamic lifetime, similar to new()
or taking the address of a composite literal, then you can assign the result of the function call to a variable and take the address of that.
答案3
得分: 5
最后,你提议Go应该允许你获取任何表达式的地址,例如:
i,j := 1,2
var p *int = &(i+j)
println(*p)
当前的Go编译器会打印错误:cannot take the address of i + j
在我看来,允许程序员获取任何表达式的地址:
- 看起来并不是很有用(也就是说,在实际的Go程序中出现的概率非常小)。
- 这会使编译器和语言规范变得复杂。
为了获得很小的收益而使编译器和规范变得复杂似乎是逆生产的。
英文:
In the end you are proposing that Go should allow you to take the address of any expression, for example:
i,j := 1,2
var p *int = &(i+j)
println(*p)
The current Go compiler prints the error: cannot take the address of i + j
In my opinion, allowing the programmer to take the address of any expression:
- Doesn't seem to be very useful (that is: it seems to have very small probability of occurrence in actual Go programs).
- It would complicate the compiler and the language spec.
It seems counterproductive to complicate the compiler and the spec for little gain.
答案4
得分: 1
我最近对类似的事情感到非常困惑。
首先,在你的例子中谈论字符串是一个干扰,使用一个结构体代替,重写成这样:
func a() MyStruct {
/* 无关紧要 */
}
var b *MyStruct = &a()
这段代码无法编译,因为你不能取a()的地址。所以可以这样做:
func a() MyStruct {
/* 无关紧要 */
}
tmpA := a()
var b *MyStruct = &tmpA
这样就可以编译了,但是你返回了一个在栈上的MyStruct,为存储MyStruct分配了足够的堆空间,然后将内容从栈复制到堆。如果你想避免这种情况,可以这样写:
func a2() *MyStruct {
/* 无关紧要,只要MyStruct在堆上创建(例如使用'new') */
}
var a *MyStruct = a2()
复制通常是廉价的,但是这些结构体可能很大。当你想修改结构体并使其“粘性”时,你不能复制然后修改副本。
无论如何,当你使用interface{}作为返回类型时,情况变得更有趣。interface{}可以是结构体或结构体的指针。同样的复制问题会出现。
英文:
I recently was tied up in knots about something similar.
First talking about strings in your example is a distraction, use a struct instead, re-writing it to something like:
func a() MyStruct {
/* doesn't matter */
}
var b *MyStruct = &a()
This won't compile because you can't take the address of a(). So do this:
func a() MyStruct {
/* doesn't matter */
}
tmpA := a()
var b *MyStruct = &tmpA
This will compile, but you've returned a MyStruct on the stack, allocated sufficient space on the heap to store a MyStruct, then copied the contents from the stack to the heap. If you want to avoid this, then write it like this:
func a2() *MyStruct {
/* doesn't matter as long as MyStruct is created on the heap (e.g. use 'new') */
}
var a *MyStruct = a2()
Copying is normally inexpensive, but those structs might be big. Even worse when you want to modify the struct and have it 'stick' you can't be copying then modifying the copies.
Anyway, it gets all the more fun when you're using a return type of interface{}. The interface{} can be the struct or a pointer to a struct. The same copying issue comes up.
答案5
得分: 1
你在将结果赋值给一个新变量时,不能直接获取结果的引用,但是你可以通过一种惯用的方式来实现,而不需要使用临时变量(它是无用的),只需预先声明你的“b”指针 - 这是你错过的真正步骤:
func a() string {
return "doesn't matter"
}
b := new(string) // b是一个指向空字符串的指针(“零值”)
*b = a() // b现在是指向`a()`结果的指针
*b
用于解引用指针并直接访问保存数据的内存区域(当然是在堆上)。
在这里尝试代码:https://play.golang.org/p/VDhycPwRjK9
英文:
You can't get the reference of the result directly when assigning to a new variable, but you have idiomatic way to do this without the use of a temporary variable (it's useless) by simply pre-declaring your "b" pointer - this is the real step you missed:
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
is used to dereference the pointer and directly access the memory area which hold your data (on the heap, of course).
Play with the code: https://play.golang.org/p/VDhycPwRjK9
答案6
得分: 1
是的,当API要求使用*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"))
英文:
Yeah, it can be annoying when APIs require the use of *string
inputs even though you’ll often want to pass literal strings to them.
For this I make a very tiny function:
// Return pointer version of string
func p(s string) *string {
return &s
}
and then instead of trying to call foo("hi")
and getting the dreaded cannot use "hi" (type string) as type *string in argument to foo
, I just wrap the argument in a call to to p()
:
foo(p("hi"))
答案7
得分: 0
a()
不指向堆栈上的变量。你不能指向堆栈(你为什么要这样做?)。
如果你想要这样做,你可以这样:
va := a()
b := &va
但是你真正想要实现的目标有些不清楚。
英文:
a()
doesn't point to a variable as it is on the stack. You can't point to the stack (why would you ?).
You can do that if you want
va := a()
b := &va
But what your really want to achieve is somewhat unclear.
答案8
得分: 0
在编写本文时,没有一个答案真正解释了为什么会出现这种情况的理由。
考虑以下代码:
func main() {
m := map[int]int{}
val := 1
m[0] = val
v := &m[0] // 不会编译,但假设它可以编译通过
delete(m, 0)
fmt.Println(v)
}
如果这段代码片段实际上编译通过了,v
会指向什么?由于底层对象已被删除,它是一个悬空指针。
鉴于此,禁止对临时对象进行寻址似乎是一个合理的限制。
英文:
At the time of writing this, none of the answers really explain the rationale for why this is the case.
Consider the following:
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)
}
If this code snippet actually compiled, what would v
point to!? It's a dangling pointer since the underlying object has been deleted.
Given this, it seems like a reasonable restriction to disallow addressing temporaries
答案9
得分: -1
猜测你需要从更有效的Cpp中获得帮助
临时对象和右值
> “在C++中,真正的临时对象是看不见的-它们不会出现在你的源代码中。它们在非堆对象被创建但没有命名时产生。这种未命名的对象通常出现在两种情况下:当应用隐式类型转换以使函数调用成功时,以及当函数返回对象时。”
来自Primer Plus的内容
> 左值是一个可以通过用户引用地址的数据对象(命名对象)。非左值包括文字常量(除了被它们的地址表示的引号字符串),以及具有多个项的表达式,例如(a + b)。
在Go语言中,字符串字面量将被转换为StrucType
对象,这将是一个不可寻址的临时结构对象。在这种情况下,字符串字面量不能在Go中通过地址引用。
好了,最后但并非最不重要的是,在Go中有一个例外,你可以获取复合字面量的地址。天哪,真是一团糟。
英文:
guess you need help from More effective Cpp
Temp obj and rvalue
> “True temporary objects in C++ are invisible - they don't appear in your source code. They arise whenever a non-heap object is created but not named. Such unnamed objects usually arise in one of two situations: when implicit type conversions are applied to make function calls succeed and when functions return objects.”
And from Primer Plus
> lvalue is a data object that can be referenced by address through user (named object). Non-lvalues include literal constants (aside from the quoted strings, which are represented by their addresses), expressions with multiple terms, such as (a + b).
In Go lang, string literal will be converted into StrucType
object, which will be a non-addressable temp struct object. In this case, string literal cannot be referenced by address in Go.
Well, the last but not the least, one exception in go, you can take the address of the composite literal. OMG, what a mess.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论