-1

以下代码尝试显示与 struct 关联的方法的地址。

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)) 
    
4

1 回答 1

3

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

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

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)

通过反射 API 中的方法表达式reflect.Value创建的A设置了方法位。正如注释所述和代码所示,Pointer 方法为以这种方式创建的所有方法表达式返回相同的值。flagMethod

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

该程序的输出显示了所有组合:

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 为通过反射 API 创建的所有方法表达式返回相同的值。这包括不同类型的方法。

我们还可以观察到,对于给定类型和方法的所有方法表达式Value.Pointer返回相同的值。这适用于绑定到不同值的方法表达式。

Value.Pointer文档说:

如果 v 的 Kind 是 Func,则返回的指针是底层代码指针,但不一定足以唯一标识单个函数。唯一的保证是当且仅当 v 是一个 nil func 值时结果为零。

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

于 2019-01-22T16:49:44.503 回答