Немного о пустых интерфейсах. Быстрый взгляд изнутри

от автора

Всем привет!

Warning: статья не принесет ничего нового для профи, но будет полезна новичкам.

Если вы это читаете, значит я уже мертв вы, как минимум, интересуетесь языком Go. Следовательно, знаете, о такой вещи, как interface{}.
А что будет, если я скажу, что любой интерфейс это просто структура? А так-же, что довольно легко реализовать самому свои интерфейсы? Прошу под кат.

Давайте начнем с предельно простого. Напишем простенькую программу на Go:

package main  import "fmt"  func ifacePrint(a interface{}) { 	fmt.Println(a) }  func main() { 	ifacePrint("Habrahabr") } 

Всё просто — main, который вызывает метод ifacePrint и собственно ifacePrint, который явно принимает один аргумент типа interface{}.
Если мы её запустим, то получим вывод строки «Habrahabr». Но нам это не интересно,
поэтому мы запустим нашу программку под надзором gdb.
И так, go build main.go && gdb ./main.
Ставим breakpoint на наш метод:
(gdb) b main.ifacePrint
Breakpoint 1 at 0x401000: file /tmp/main.go, line 5.

И запускаем программу:
(gdb) run
Starting program: /tmp/main
[New LWP 4892]
[New LWP 4893]
[New LWP 4894]
[New LWP 4895]

Breakpoint 1, main.ifacePrint (a=...) at /tmp/main.go:5
5 func ifacePrint(a interface{}) {

gdb тут же останавливает выполнение, дойдя до бряка.
Давайте посмотрим, какого типа у нас переменная а:
(gdb) whatis a
type = interface {}

Что ж, логично, interface{}, ну, заглянем, тогда, что ли в её содержимое?
(gdb) p a
$1 = {_type = 0x4b8e00, data = 0xc8200761b0}

Интересный поворот, мы же должны были отправить строку…
Однако, это и есть наша строка, точнее её interface, в поле data лежит указатель, на область в памяти, в которой и содержится наша строка, а _type — указатель на внутренюю (рантайма) структуру TypeDescriptor.
Забегая вперёд и в сторону покажу, как я делал интерфейсы для своей ОС на Go (gccgo):

//Реализация пустого интерфейса type EmptyInterface struct {   __type_descriptor *TypeDescriptor // Поинтер на структуру - описание типа   __object          uintptr // Поинтер на данные }  // Структура - описание типа type TypeDescriptor struct {   kind         uint8 //Код типа данных, можно считать за ID   align        uint8 //Выравнивание в памяти   fieldAlign   uint8 //Выравнивание свойств (если есть)   size         uintptr //Размер   hash         uint32 /   hashfn       uint32 //TODO   equalfn      uint32 //TODO   gc           uintptr //TODO   string       *string //Строковое описание типа ("string", "int")   uncommonType *Uncommon //Дополнительное описание для не встроеных типов   ptrToThis    *TypeDescriptor // Описание указателя на данный тип } //Дополнительное описание для пользовательских типов type Uncommon struct {    name          *string //Имя типа данных   pkgPath      *string //Имя пакета, в котором описан  methods uintptr //TODO Указатель на массив методов, в которых ресивером является данный тип данных }  

В «классическом» Go — всё примерно так-же:
(gdb) p *a._type
$7 = {size = 16, ptrdata = 8, hash = 3774831796, _unused = 0 '\000', align = 8 '\b', fieldalign = 8 '\b', kind = 24 '\030',
alg = 0x582860 <runtime.algarray+224>,
gcdata = 0x52a3ac "\001\002\003\004\005\006\a\b\t\n\r\016\017\020\022\025\026\031\032\033\037,568<?AUr~\236\237\325\365\370\377", _string = 0x50e5e0, x = 0x4b8e40, ptrto = 0x4b1ba0}

Здесь мы видим примерно всю ту же информацию о типа данных «скрывающемся» за пустым интерфейсом.
(gdb) p a._type._string
$11 = (struct string *) 0x50e5e0
(gdb) p *a._type._string
$12 = 0x4fdfb0 "string"
(gdb) p *a._type.x
$13 = {name = 0x50e5e0, pkgpath = 0x0, mhdr = {array = 0x4b8e68, len = 0, cap = 0}}
(gdb) p *a._type.x.name
$14 = 0x4fdfb0 "string"
(gdb) p *a._type.ptrto
$15 = {size = 8, ptrdata = 8, hash = 1511480045, _unused = 0 '\000', align = 8 '\b', fieldalign = 8 '\b', kind = 54 '6',
alg = 0x5827d0 <runtime.algarray+80>,
gcdata = 0x52a3ac "\001\002\003\004\005\006\a\b\t\n\r\016\017\020\022\025\026\031\032\033\037,568<?AUr~\236\237\325\365\370\377", _string = 0x5045d0, x = 0x0, ptrto = 0x0}
(gdb) p *a._type.ptrto._string
$16 = 0x4fc2b8 "*string"
(gdb) p *a._type.alg
$17 = {hash = {void (void *, uintptr, uintptr *)} 0x582860 <runtime.algarray+224>, equal = {void (void *, void *,
bool *)} 0x582860 <runtime.algarray+224>}
(gdb) p *a._type.alg.hash
$18 = {void (void *, uintptr, uintptr *)} 0x582860 <runtime.algarray+224>
(gdb) p *a._type.alg.equal
$19 = {void (void *, void *, bool *)} 0x582868 <runtime.algarray+232>

Думаю, на сей ноте можно закончить повествование. Я вас немного просветил и показал направление куда копать, а копать или нет — пусть каждый решит сам 😉

P.S. Всем желающим разобраться с runtime Go рекомендую ковырять свои програмки под gdb и читать исходники gccgo и Go.
ссылка на оригинал статьи https://habrahabr.ru/post/315260/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *