go - 解释方法表达式的打印值

标签 go memory-address func reflect

以下代码试图显示与结构关联的方法的地址。

package  main

import (
    "fmt"
    "reflect"
)

type II interface {
    Callme()
}

type Str struct {
    I int
    S string
}

func (s *Str) Callme () {
    fmt.Println("it is me")
}

func main() {
    s0 := &Str{}
    t := reflect.TypeOf(s0)
    v := reflect.ValueOf(s0)
    fmt.Println("Callme ", s0.Callme)   //real address ?
    fmt.Println(t.Method(0).Name, v.Method(0))    //real address ?

    s1 := &Str{}
    t1 := reflect.TypeOf(s1)
    v1 := reflect.ValueOf(s1)
    fmt.Println("Callme ", s1.Callme)   //real address ?
    fmt.Println(t1.Method(0).Name, v1.Method(0))    //real address ?
}

输出是:

Callme  0x4bc2d0
Callme 0x4ab2c0
Callme  0x4bc2d0
Callme 0x4ab2c0

所以我有两个问题:

  • 首先,为什么这些语句不显示相同的值?

    fmt.Println("Callme ", s0.Callme)
    fmt.Println(t.Method(0).Name, v.Method(0))
    
  • 其次,为什么这些语句显示相同的值?

    fmt.Println(t.Method(0).Name, v.Method(0))    
    fmt.Println(t1.Method(0).Name, v1.Method(0)) 
    

最佳答案

fmt 包调用 Value.Pointer获取函数地址。

让我们看一下 Value.Pointer 为函数返回的内容的示例:

s0 := &Str{}
v0 := reflect.ValueOf(s0)
fmt.Printf("s0.Callme: %0x %0x\n", reflect.ValueOf(s0.Callme).Pointer(), s0.Callme)
fmt.Printf("v0.Method(0) %0x %0x\n", v0.Method(0).Pointer(), v0.Method(0))

s1 := &Str{}
v1 := reflect.ValueOf(s1)
fmt.Printf("s1.Callme %x %x\n", reflect.ValueOf(s1.Callme).Pointer(), s1.Callme)
fmt.Printf("v1.Method(0) %x %x\n", v1.Method(0).Pointer(), v1.Method(0))

输出是:

s0.Callme: 105240 105240
v0.Method(0) eee60 eee60
s1.Callme 105240 105240
v1.Method(0) eee60 eee60

这与问题中显示的模式相匹配。

function related code for Value.Pointer是:

    if v.flag&flagMethod != 0 {
        // As the doc comment says, the returned pointer is an
        // underlying code pointer but not necessarily enough to
        // identify a single function uniquely. All method expressions
        // created via reflect have the same underlying code pointer,
        // so their Pointers are equal. The function used here must
        // match the one used in makeMethodValue.
        f := methodValueCall
        return **(**uintptr)(unsafe.Pointer(&f))
    }
    p := v.pointer()
    // Non-nil func value points at data block.
    // First word of data block is actual code.
    if p != nil {
        p = *(*unsafe.Pointer)(p)
    }
    return uintptr(p)

通过 method expression 创建的 reflect.Value在 reflect API 中设置了 flagMethod 方法位。如评论所述和代码所示,Pointer 方法为以这种方式创建的所有方法表达式返回相同的值。

relect.ValueOf(s1.Callme) 创建的 reflect.Value 没有设置 flagMethod 方法位。在这种情况下,该函数返回指向实际代码的指针。

this program 的输出显示所有组合:

type StrA struct {
    I int
    S string
}

func (s *StrA) Callme() {
    fmt.Println("it is me")
}

type StrB struct {
    I int
    S string
}

func (s *StrB) Callme() {
    fmt.Println("it is me")
}

s0A := &StrA{}
v0A := reflect.ValueOf(s0A)
s1A := &StrA{}
v1A := reflect.ValueOf(s0A)

fmt.Println("s0A.Callme ", reflect.ValueOf(s0A.Callme).Pointer())
fmt.Println("v0A.Method(0) ", v0A.Method(0).Pointer())
fmt.Println("s1A.Callme ", reflect.ValueOf(s1A.Callme).Pointer())
fmt.Println("v1A.Method(0) ", v1A.Method(0).Pointer())

s0B := &StrB{}
v0B := reflect.ValueOf(s0B)
s1B := &StrB{}
v1B := reflect.ValueOf(s0B)

fmt.Println("s0B.Callme ", reflect.ValueOf(s0B.Callme).Pointer())
fmt.Println("v0B.Method(0) ", v0B.Method(0).Pointer())
fmt.Println("s1B.Callme ", reflect.ValueOf(s1B.Callme).Pointer())
fmt.Println("v1B.Method(0) ", v1B.Method(0).Pointer())

输出:

s0A.Callme  1061824
v0A.Method(0)  978528
s1A.Callme  1061824
v1A.Method(0)  978528
s0B.Callme  1061952
v0B.Method(0)  978528
s1B.Callme  1061952
v1B.Method(0)  978528

我们可以观察到 Value.Pointer 对所有 method expressions 返回相同的值通过反射 API 创建。这包括不同类型的方法。

我们还可以观察到 Value.Pointer 为所有 method expressions 返回相同的值在给定的类型和方法上。这适用于绑定(bind)到不同值的方法表达式。

Value.Pointer documentation说:

If v's Kind is Func, the returned pointer is an underlying code pointer, but not necessarily enough to identify a single function uniquely. The only guarantee is that the result is zero if and only if v is a nil func Value.

鉴于此,应用程序无法通过 fmt 包可靠地使用 Value.Pointer 或打印值来比较函数和方法。

关于go - 解释方法表达式的打印值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54304608/

相关文章:

performance - golang lambda 中的快速重新发送响应

api - 同一端点的 Multi-Tenancy 实现

c# - 如何获取 dll 函数的 id(内存地址)?

perl - 停止 perl 重载或打印引用的内存 "address"

c# - 具有多个参数的函数方差

c# - 扩展 T 的扩展方法 - 不好的做法?

GoCQL : Marshal string into timestamp

go - Kubernetes Helm Chart If 条件检查

debugging - 为什么GDB在寄存器信息期间用5填充内存地址的0?

Swift func参数奇怪