Интерфейсы в golang делятся на интерфейсы с методами и пустые интерфейсы. Интерфейс с методами представлен iface на нижнем уровне, а нижний слой пустого интерфейса представлен eface. Давайте посмотрим на принципы работы этих двух типов интерфейсов через нижний уровень.
Вот прототип интерфейса:
//runtime/runtime2.go
//非空接口
type iface struct {
tab *itab
data unsafe.Pointer
}
type itab struct {
inter *interfacetype
_type *_type
link *itab
hash uint32 // copy of _type.hash. Used for type switches.
bad bool // type does not implement interface
inhash bool // has this itab been added to hash?
unused [2]byte
fun [1]uintptr // variable sized
}
//******************************
//空接口
type eface struct {
_type *_type
data unsafe.Pointer
}
//========================
//这两个接口共同的字段_type
//========================
//runtime/type.go
type _type struct {
size uintptr
ptrdata uintptr // size of memory prefix holding all pointers
hash uint32
tflag tflag
align uint8
fieldalign uint8
kind uint8
alg *typeAlg
// gcdata stores the GC type data for the garbage collector.
// If the KindGCProg bit is set in kind, gcdata is a GC program.
// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
gcdata *byte
str nameOff
ptrToThis typeOff
}
//_type这个结构体是golang定义数据类型要用的,讲到反射文章的时候在具体讲解这个_type。
1.iface
1.1 Как типы переменных преобразуются в типы интерфейсов?
См. код ниже:
package main
type Person interface {
run()
}
type xitehip struct {
age uint8
}
func (o xitehip)run() {
}
func main() {
var xh Person = xitehip{age:18}
xh.run()
}
Переменная xh представляет собой тип интерфейса Person.Как тип структуры xitehip преобразуется в тип интерфейса? Взгляните на сгенерированный ассемблерный код:
0x001d 00029 (main.go:13) PCDATA $2, $0
0x001d 00029 (main.go:13) PCDATA $0, $0
0x001d 00029 (main.go:13) MOVB $0, ""..autotmp_1+39(SP)
0x0022 00034 (main.go:13) MOVB $18, ""..autotmp_1+39(SP)
0x0027 00039 (main.go:13) PCDATA $2, $1
0x0027 00039 (main.go:13) LEAQ go.itab."".xitehip,"".Person(SB), AX
0x002e 00046 (main.go:13) PCDATA $2, $0
0x002e 00046 (main.go:13) MOVQ AX, (SP)
0x0032 00050 (main.go:13) PCDATA $2, $1
0x0032 00050 (main.go:13) LEAQ ""..autotmp_1+39(SP), AX
0x0037 00055 (main.go:13) PCDATA $2, $0
0x0037 00055 (main.go:13) MOVQ AX, 8(SP)
0x003c 00060 (main.go:13) CALL runtime.convT2Inoptr(SB)
0x0041 00065 (main.go:13) MOVQ 16(SP), AX
0x0046 00070 (main.go:13) PCDATA $2, $2
0x0046 00070 (main.go:13) MOVQ 24(SP), CX
Есть функция преобразования, найденная из сборки: Runtime.convt2inoPTR (SB) Давайте посмотрим на реализацию этой функции:
func convT2Inoptr(tab *itab, elem unsafe.Pointer) (i iface) {
t := tab._type
if raceenabled {
raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2Inoptr))
}
if msanenabled {
msanread(elem, t.size)
}
x := mallocgc(t.size, t, false)//为elem申请内存
memmove(x, elem, t.size)//将elem所指向的数据赋值到新的内存中
i.tab = tab //设置iface的tab
i.data = x //设置iface的data
return
}
Из приведенной выше реализации мы обнаруживаем, что исходные данные структуры, сгенерированные компилятором, будут скопированы, а затем новый адрес данных будет присвоен iface.data для создания полного iface, так что xh в следующем исходном коде преобразуется в тип интерфейса Person.
var xh Person = xitehip{age:18}
Взгляните на реальную работу с gdb (см. рис. 1):
Функция convT2Inoptr передает параметры *itab и *xitehip в исходном коде. На рисунке 2 показан прототип типа itab и данные в памяти.Обнаружено, что itab действительно является полем в исходном коде во время выполнения. Всего 32 байта. ([4]uint8 не занимает байтов)
1.2 Как тип переменной указателя преобразуется в тип интерфейса?
Или вышеприведенный пример просто преобразует
var xh Person = xitehip{age:18}
заменяется
var xh Person = &xitehip{age:18}
Как переменная типа указателя преобразуется в тип интерфейса? См. ассемблерный код ниже:
0x001d 00029 (main.go:13) PCDATA $2, $1
0x001d 00029 (main.go:13) PCDATA $0, $0
0x001d 00029 (main.go:13) LEAQ type."".xitehip(SB), AX
0x0024 00036 (main.go:13) PCDATA $2, $0
0x0024 00036 (main.go:13) MOVQ AX, (SP)
0x0028 00040 (main.go:13) CALL runtime.newobject(SB)
0x002d 00045 (main.go:13) PCDATA $2, $1
0x002d 00045 (main.go:13) MOVQ 8(SP), AX
0x0032 00050 (main.go:13) MOVB $18, (AX)
Нашел эту функцию:
runtime.newobject(SB)
Давайте посмотрим на конкретную реализацию:
// implementation of new builtin
// compiler (both frontend and SSA backend) knows the signature
// of this function
func newobject(typ *_type) unsafe.Pointer {
return mallocgc(typ.size, typ, true)
}
Компилятор автоматически генерирует iface и назначает адрес объекта, созданного &xitehip{age:18} (через newobject), в iface.data. То есть структура xitehip не копируется. Взгляните на рисунок 5 с gdb:
1.3 Как XH находит метод прогона? Мы продолжаем видеть рисунок 6, а соответствующие объяснения были отмечены на рисунке:
1.4 Правила вызова интерфейса
Добавьте к приведенному выше примеру метод интерфейса EAT() и реализуйте его (обратите внимание, что получателем этого метода интерфейса является указатель).
package main
type Person interface {
run()
eat(string)
}
type xitehip struct {
age uint8
}
func (o xitehip)run() { // //接收方o是值
}
func (o *xitehip)eat(food string) { //接收方o是指针
}
func main() {
var xh Person = &xitehip{age:18} //xh是指针
xh.eat("ma la xiao long xia!")
xh.run()
}
Фактическим типом переменной xh в этом примере является указатель, так как же он вызывает метод run без указателя? Продолжайте отслеживать с помощью gdb, как показано на рисунке 7:
Таким образом, объект указательного типа вызывает метод получателя неуказательного типа, а компилятор автоматически преобразует получателя в указательный тип, вызывающий объект находит соответствующий список инструкций метода через массив xh.tab.fun.
Тогда xh — это интерфейс типа значения, а приемник метода, реализованного интерфейсом, — типа указателя.Может ли вызывающая сторона вызвать этот метод указателя?Ответ в том, что он не только не может даже скомпилироваться, см. рисунок 11:
| абонент | получатель | Может ли он быть скомпилирован |
|---|---|---|
| ценность | ценность | true |
| ценность | указатель | false |
| указатель | ценность | true |
| указатель | указатель | true |
| указатель | указатели и значения | true |
| ценность | указатели и значения | false |
Из вышеприведенной таблицы можно сделать следующие выводы:
Когда вызывающий объект является значением, компилятор не разрешает компиляцию, пока получатель имеет метод указателя.
2 eface
Пустой интерфейс не имеет списка методов относительно непустого интерфейса.
type eface struct {
_type *_type
data unsafe.Pointer
}
Первый атрибут изменен с itab на _type, эта структура является основой типов переменных в golang, поэтому пустой интерфейс может указывать любой тип переменной.
2.1 Пример:
cpackage main
import "fmt"
type xitehip struct {
}
func main() {
var a interface{} = xitehip{}
var b interface{} = &xitehip{}
fmt.Println(a)
fmt.Println(b)
}
GDB спросил на рисунке 12:
2.2 Утверждения
Определить тип данных переменной
s, ok := i.(TypeName)
if ok {
fmt.Println(s)
}
Если нет ok, неправильный тип вызовет панику.
Вы также можете использовать форму переключения:
switch v := v.(type) {
case TypeName:
...
}
3 Проверьте интерфейс
3.1 Используйте компилятор для проверки реализации интерфейса
var _ InterfaceName = (*TypeName)(nil)
3.2 нулевой и нулевой интерфейс
3.2.1 nil
func main() {
var i interface{}
if i == nil {
println(“The interface is nil.“)
}
}
(gdb) info locals;
i = {_type = 0x0, data = 0x0}
3.2.2 Если внутреннее значение данных интерфейса равно нулю, но вкладка не пуста, интерфейс является нулевым интерфейсом.
// go:noinline
func main() {
var o *int = nil
var i interface{} = o
if i == nil {
println("Nil")
}
println(i)
}
(gdb) info locals;
i = {_type = 0x21432f8 <type.*+36723>, data = 0x0}
o = 0x0
3.2.3 Проверка с отражением
v := reflect.ValueOf(a)
if v.Isvalid() {
println(v.IsNil()) // true, This is nil interface
}
Ссылаться на
Анализ реализации интерфейса Go — облачная технология Xiaomi
10 вопросов об интерфейсе в углубленной расшифровке языка Go