Why does inline instantiation of variable requires explicitly taking the address of it to call pointer method, while for a existing var its implict

huangapple go评论67阅读模式
英文:

Why does inline instantiation of variable requires explicitly taking the address of it to call pointer method, while for a existing var its implict

问题

这种行为有什么原因吗?我想知道在内存层面有什么不同。编译器返回"cannot take the address of composite literal",而我可以明确要求它这样做。

这是Go Playground的链接:

u := User{"john"}
fmt.Println(u.Name()) //implicit

//fmt.Println(User{"john"}.Name()) //Error: cannot call pointer method on composite literal, cannot take the address of composite literal
fmt.Println((&User{"jim"}).Name()) //explicit

type User struct {
    name string
}

func (u *User) Name() string {
    return u.name
}

链接:go playground链接

英文:

Is there a reason for this behaviour? I would like to know what is different in the memory level. The compiler returns "cannot take the address of composite literal" while i can explicitly ask it to do it.

Heres the go playground go playground link

u := User{"john"}
fmt.Println(u.Name()) //implicit

//fmt.Println(User{"john"}.Name()) //Error: cannot call pointer method on composite literal, cannot take the address of composite literal
fmt.Println((&User{"jim"}).Name()) //explicit

type User struct {
	name string
}

func (u *User) Name() string {
	return u.name
}

答案1

得分: 13

因为一个复合字面量在被赋值给变量之前是不可寻址的

> 操作数必须是可寻址的,即变量、指针间接引用或切片索引操作;或者是可寻址结构操作数的字段选择器;或者是可寻址数组的数组索引操作。
作为对可寻址要求的例外,x 也可以是一个(可能带括号的)复合字面量。

你可以取到 User{"jim"} 的地址:(&User{"jim"})(字面量指针)。
但是你不能直接使用 User{"jim"} 的值(字面量值,直到它被赋值给一个变量)。
这个讨论串中可以了解更多信息。


这个讨论串对“表达式不可寻址”问题进行了补充:

> 取某个东西的地址,这个东西在这个假设的点之前是不可寻址的(一个表达式,不巧的是没有与标识符相关联),在语义上与设置目标指针的解引用值没有任何区别。
换句话说,下面的代码是等价的:

var x *int
*x = (2 * 3) + 4

var x *int
x = &((2 * 3) + 4)

> 由于一个表达式在使用后就被丢弃了,它的值的地址(通常完全驻留在寄存器中,因此实际上没有地址)在任何情况下都是毫无意义的,取它的地址的唯一好处是在变量声明中的类型推断的语法便利性(最多节省一行代码):

x := &((2 * 3) + 4)

> 使任意表达式可寻址的一个问题

m := map[int]int{1:2}
x := &m[1]
x = 3
fmt.Println("x is", x, "while m[1] is", m[1])
// 将打印 `x is 3 while m[1] is 2`

> 简而言之:不清楚(几乎不可能调试)你是否正在取一个你期望可寻址的东西的地址,或者当你取一个隐式中间变量的地址时(这往往是你不想要的)。

英文:

Because a composite litteral is not addressable until it's been assigned to a variable:

> The operand must be addressable, that is, either a variable, pointer indirection, or slice indexing operation; or a field selector of an addressable struct operand; or an array indexing operation of an addressable array.
As an exception to the addressability requirement, x may also be a (possibly parenthesized) composite literal.

You can take the address to User{"jim"}: (&User{"jim"}) (literal pointer).
But you cannot directly use the value of User{"jim"} (literal value, not until it is assign to a variable).
See more in this thread.


This thread adds to the"Expression isn't addressable" issue:

> Taking the address of something, that up to this hypothetical point was not addressable (an expression that doesn't happen to be rooted to an identifier), is semantically no different then setting the dereferenced value of the destination pointer.
In other words, the following are equivalent:

var x *int
*x = (2 * 3) + 4

var x *int
x = &((2 * 3) + 4)

> Since an expression is thrown away as soon as it's used, the address of its value (which often is entirely register-resident, and thus doesn't actually have an address) is entirely meaningless in any case, and the only benefit from taking it's address is the syntactic convenience of type-inference in variable declarations (saving at most a single line):

x := &((2 * 3) + 4)

> A problem with making arbitrary expressions addressable:

m := map[int]int{1:2}
x := &m[1]
x = 3
fmt.Println("x is", x, "while m[1] is", m[1])
// will print `x is 3 while m[1] is 2`

> In short: it'll be unclear (and nigh impossible to debug) whether you're taking the address of something you expect to be addressable, or when you're taking the address of an implicit intermediate var (which is very often what you don't want).

答案2

得分: 2

这种行为的原因是针对特定表示法的可寻址性有一个例外情况,而这个例外情况的原因由Go的维护者之一在这里解释:

它们是特殊的,因为我们决定这种表示法对于构造函数来说足够有用,值得特殊处理:

对复合字面量进行取地址操作(§Address operators)
会生成指向字面量值实例的唯一指针。

http://golang.org/doc/go_spec.html#Composite_literals

Russ

"对复合字面量进行取地址操作会生成指向字面量值实例的唯一指针",这句话的意思是每次执行&Object{}这种表示法时,它都会创建一个新的值实例,并创建一个指向该新值的新指针。这种可寻址性例外仅仅是由于这种字面量的表示法不同于其他可寻址对象,因为它没有被&操作符所作用。

我认为这种表示法对于逃逸分析很有帮助,逃逸分析在编译时确定引用是否逃逸出函数,并确定它应该在堆上还是栈上分配(更快)。据我所了解,那些取地址的变量被视为逃逸分析的候选对象。由于Go的主要目标之一是快速编译,我认为需要清晰地创建和跟踪引用。

例如,对于Item{}.Method(),是否需要创建指针并因此对Item{}值进行逃逸分析取决于Method()的接收器类型是*Item还是Item,这会不必要地复杂化编译过程。这就是为什么你要使用显式的表示法&Item{}来构造*Item的原因。

英文:

> Is there a reason for this behaviour?

The reason for the behavior is that there is an exception to addressability for that specific notation and the reason for that exception is explained here by one of the Go maintainers :

> They're special because we decided that the notation was useful
> enough for constructors to merit a special case:
>
>> Taking the address of a composite literal (§Address operators)
>> generates a unique pointer to an instance of the literal's value.
>>
>> http://golang.org/doc/go_spec.html#Composite_literals
>
> Russ


> "Taking the address of a composite literal (§Address operators)
> generates a unique pointer to an instance of the literal's value."
> Could you elaborate that?

I think that means the notation &Object{} notation is simply a literal pointer each time it is executed it creates a new instance of the value and a new pointer is created that points to the new value. The addressability exception simply results from the notation of this literal, it is unlike the other addressables because it is not being acted upon by the & operator.

> I would like an answer similar to jesse's on
> the "here" link you mentioned. That is for C. Can anyone confirm that
> for golang?

My guess is this that this notation is helpful with escape analysis, which determines at compile time whether a references escapes the function and determines whether it should be allocated on the heap or the stack ( faster ). From what I understand the variables that have their address taken are considered candidates for escape analysis. Since one of Go's main goals is fast compilation I think reference creation and tracking is required to be clear.

For example, for Item{}.Method() the need to create a pointer and therefore run escape analysis to the Item{} value would depend on the receiver type of the Method() whether it is *Item or just Item this unnecessarily complicates compilation. That's why you use the explicit notation &Item{} to construct a *Item.

huangapple
  • 本文由 发表于 2014年9月1日 16:33:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/25601802.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定