Golang中接口与结构体的意外行为/数据问题
Golang中接口与结构体的意外行为/数据问题
我一直难以理解,为什么当我从 Integer 结构体中移除 additional 字段时,下面的代码块会打印出数字值 2 而不是 0。
package main
import (
"fmt"
)
type Number interface {
Value() int
}
type Integer struct {
value int
additional int
}
func (integer Integer) Value() int{
return integer.value
}
func main() {
number, err := getNumber()
if err != nil {
panic(err)
}
fmt.Printf("number val: %d\n", number.Value())
}
func getNumber() (Number, error) {
var intNumber Integer
return intNumber, setInteger(&intNumber)
}
func setInteger(num *Integer) error{
num.value = 2
return nil
}
更多关于Golang中接口与结构体的意外行为/数据问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html
我已编辑问题以澄清我的疑问,抱歉之前的解释不够清楚。
更多关于Golang中接口与结构体的意外行为/数据问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
我不是想打印出2,我想问的是,当我从Integer结构体中移除additional字段时,为什么它会打印出2。
是的,我明白了。我也不知道为什么,我只是想为这个问题提供更多数据,希望能帮助找到答案。: )
啊,抱歉,没看到更新。
不知为何,行为也取决于返回类型。如果我将第一个返回值从 Number 改为 Integer,那么无论字段如何,它总是返回相同的结果。
当我移除 additional 字段,不改变求值顺序但结果却发生了变化,你如何解释这一点?我原以为可能是编译优化做了什么,我禁用了优化和内联,但结果仍然相同。
感谢您的回答,但我再次强调,我不是在问如何打印 0 或 2,我想知道为什么这一个字段会改变结果?背后发生了什么,这是编译优化、一个错误,还是我哪里做错了(显然我没有)?
嗯,你通过 & 引用了 Number,这意味着在返回值之前,需要先通过函数进行计算,这样你就得到了之前设置为 2 的 Number.Value() 的值。
说实话,这个结构真的让我很困惑 :),这只是一个理论问题,还是来自生产代码的示例?
如果是后者,我可能会考虑改变代码的结构设计。
我不太理解这种行为,但只是稍微修改了代码,
https://play.golang.org/p/IVit28szGXh
将对 setInteger 的调用移出返回语句,现在无论是否添加额外代码,它都能按预期工作。也许这个改动有助于理解此处的行为。
func main() {
fmt.Println("hello world")
}
我在运行 go build 之前成功设置了 GOSSAFUNC=getNumber 环境变量,这让我在优化器遍历生成的 SSA 时获得了一系列差异。我看到 setNumber 中的赋值在“后期优化”(“阶段”,是吗?)中开始被优化掉,然后在“死自动消除”“阶段”中进一步优化,最后在“通用死代码”阶段中赋值完全消失。我想我会尝试从这些优化器中获取更多输出。
func main() {
fmt.Println("hello world")
}
func getNumber() (Number, error) {
var intNumber Integer
return intNumber, setInteger(&intNumber)
}
return 语句的操作数——intNumber 和 setInteger(&intNumber)——的求值顺序是未指定的。它是未定义的,可能会变化。
为了保证顺序,请这样写:
func getNumber() (Number, error) {
var intNumber Integer
err := setInteger(&intNumber)
return intNumber, err
}
package main
import (
"fmt"
)
type Number interface {
Value() int
}
type Integer struct {
value int
additional int
}
func (integer Integer) Value() int {
return integer.value
}
func main() {
number, err := getNumber()
if err != nil {
panic(err)
}
fmt.Printf("number val: %d\n", number.Value())
}
func getNumber() (Number, error) {
var intNumber Integer
err := setInteger(&intNumber)
return intNumber, err
}
func setInteger(num *Integer) error {
num.value = 2
return nil
}
https://play.golang.org/p/9K8TRe3uq6Z
number val: 2
这是因为Go语言中接口值的底层表示导致的。当接口类型变量存储具体类型的值时,它包含两部分信息:动态类型和动态值。
在你的代码中,关键问题出现在 getNumber() 函数中:
func getNumber() (Number, error) {
var intNumber Integer
return intNumber, setInteger(&intNumber)
}
这里发生了以下情况:
intNumber被声明为Integer类型的零值setInteger(&intNumber)修改了intNumber.value为 2- 但是
intNumber作为值被返回给接口Number
当结构体包含 additional 字段时,结构体的大小足以让编译器做出不同的内存布局决策。移除 additional 字段后,结构体大小改变,这影响了接口值的表示方式。
实际上,问题在于返回值顺序和接口值的捕获时机。更准确的解释是:在函数返回时,返回值被评估的顺序是从左到右。所以 intNumber 的值在调用 setInteger 之前就已经被确定了。
要修复这个问题,可以调整代码确保在接口捕获值之前完成修改:
func getNumber() (Number, error) {
var intNumber Integer
err := setInteger(&intNumber)
return intNumber, err
}
// 或者使用指针接收器
type Integer struct {
value int
}
func (integer *Integer) Value() int {
if integer == nil {
return 0
}
return integer.value
}
func getNumber() (Number, error) {
var intNumber Integer
setInteger(&intNumber)
return &intNumber, nil
}
这样就能确保接口值包含的是修改后的数据。

