萌新最近刚入门Go语言,遇到一个指针问题很困惑,应该是和语言机制有关系,请教各位前辈一下。
业务代码简化以后可以表达如下:
```go
package main
import "fmt"
func main() {
slist := SimpleTest()
fmt.Printf("%+#v\n", slist)
}
type S struct {
Data int
Children []S
}
func SimpleTest() (tree []S) {
tree = make([]S, 0)
points := make([]*S, 0)
for index := 0; index <= 5; index++ {
tree = append(tree, S{
Data: index,
Children: make([]S, 0),
})
//标记1
points = append(points, &(tree[index]))
}
//标记2
//for index := range tree {
// points = append(points, &(tree[index]))
//}
for index := range points {
points[index].Children = append(points[index].Children,S{
Data: points[index].Data * 10,
Children: make([]S, 0),
})
}
return
}
```
主要的问题是 单从调试输出的地址来看 标记1和标记2 产生的效果是等价的,但是标记1不能够正常为`S`结构体内部的`Children`赋值
输出的结果为
```go
[]main.S{
main.S{Data:0, Children:[]main.S{}},
main.S{Data:1, Children:[]main.S{}},
main.S{Data:2, Children:[]main.S{}},
main.S{Data:3, Children:[]main.S{}},
main.S{Data:4, Children:[]main.S{main.S{Data:40, Children:[]main.S{}}}},
main.S{Data:5, Children:[]main.S{main.S{Data:50, Children:[]main.S{}}}}
}
```
仅有 4,5 中 `Children`赋值正常(为什么?)
如果将`index`值依次变化,使用标记1的情况,输出则为
```go
// index 0 到 0 正常
[]main.S{
main.S{Data:0, Children:[]main.S{main.S{Data:0, Children:[]main.S{}}}}
}
```
```go
// index 0 到 1 不正常
[]main.S{
main.S{Data:0, Children:[]main.S{}},
main.S{Data:1, Children:[]main.S{main.S{Data:10, Children:[]main.S{}}}}
}
```
```go
// index 0 到 2 不正常
[]main.S{
main.S{Data:0, Children:[]main.S{}},
main.S{Data:1, Children:[]main.S{}},
main.S{Data:2, Children:[]main.S{main.S{Data:20, Children:[]main.S{}}}}
}
```
```go
// index 0 到 3 不正常
[]main.S{
main.S{Data:0, Children:[]main.S{}},
main.S{Data:1, Children:[]main.S{}},
main.S{Data:2, Children:[]main.S{main.S{Data:20, Children:[]main.S{}}}},
main.S{Data:3, Children:[]main.S{main.S{Data:30, Children:[]main.S{}}}}
}
```
`index >= 3` 后仅有最后两个可以正常赋值
而使用标记2 的三行代码进行实现,则正常,输出为
```go
[]main.S{
main.S{Data:0, Children:[]main.S{main.S{Data:0, Children:[]main.S{}}}},
main.S{Data:1, Children:[]main.S{main.S{Data:10, Children:[]main.S{}}}},
main.S{Data:2, Children:[]main.S{main.S{Data:20, Children:[]main.S{}}}},
main.S{Data:3, Children:[]main.S{main.S{Data:30, Children:[]main.S{}}}},
main.S{Data:4, Children:[]main.S{main.S{Data:40, Children:[]main.S{}}}},
main.S{Data:5, Children:[]main.S{main.S{Data:50, Children:[]main.S{}}}}}
```
符合预期情况
问题:
1.为什么使用标记1会出现有的可以赋值正确,有的赋值不正确(检查过`tree` 和 `points` 里的地址了,是一致的), 并且为何会出现最后两个赋值正确
2.标记1和标记2 原理上不同的地方在哪里(按照其他语言的经验,在这个例子中两者应该一样?)
Orz... 谢谢各位前辈了!
代码有写的丑陋的地方轻喷
更多评论
我觉得你可能正好触发了一个golang的优化陷阱
考察以下代码
```go
package main
import "fmt"
func main() {
slist := SimpleTest()
fmt.Printf("%+#v\n", slist)
}
type S struct {
Data int
Children []S
}
func SimpleTest() (tree []S) {
tree = make([]S, 0)
points := make([]*S, 0)
for index := 0; index <= 5; index++ {
tree = append(tree, S{
Data: index,
Children: make([]S, 0),
})
s := tree[index]
//标记1
points = append(points, &s)
}
//标记2
//for index := range tree {
// points = append(points, &(tree[index]))
//}
for index := range tree { // 区别在这里
tree[index].Children = append(tree[index].Children,S{
Data: tree[index].Data * 10,
Children: make([]S, 0),
})
}
return
}
```
输出结果
```shell
[]main.S{main.S{Data:0, Children:[]main.S{main.S{Data:0, Children:[]main.S{}}}}, main.S{Data:1, Children:[]main.S{main.S{Data:10, Children:[]main.S{}}}}, main.S{Data:2, Children:[]main.S{main.S{Data:20, Children:[]main.S{}}}}, main.S{Data:3, Children:[]main.S{main.S{Data:30, Children:[]main.S{}}}}, main.S{Data:4, Children:[]main.S{main.S{Data:40, Children:[]main.S{}}}}, main.S{Data:5, Children:[]main.S{main.S{Data:50, Children:[]main.S{}}}}}
```
你的标记1无效 标记2有效 是因为你在标记2中再次使用了返回值tree, 阻止了golang的编译器过早把tree优化到返回栈里.
标记1使用过tree之后后面没有再次使用, golang会认为tree的值不会改变了 所以会出现你发现的这个奇怪的问题.
而且你这个程序本身思路也比较奇葩, point压根就没啥用处...
#1
非常感谢解答!
1. 按照优化的思路来说,似乎标记1和标记2还是应该等价的? 至少在标记2 之后也没有再次用到tree,当然了,优化这种玄学也不排除2可以但是1不行的情况,确实是个思路
2.这个问题来源于我司的业务代码,提问不能放实际代码(太长了主要)这段代码主要意在提炼问题本质而不是讨论代码合理性,所以修改后的代码没有参考意义,这里实际上还是讨论 “为什么指针地址一样但是修改不了” 的问题。
3.在问题的`30`行的 `for`之前,我检查过两种方式,`points` 和 `tree` 的地址是一一对应的,也就是说后续的 `points` 操作的地址仍然是 `tree` 的地址,莫非优化之后,赋值就失败了吗?
最后还是再次感谢提供了新的思路
#2