В этом разделе мы изучим некоторые волшебные функции языка Go и напрямую манипулируем памятью указанного адреса памяти с помощью функций, предоставляемых встроенным пакетом unsafe. С помощью пакета unsafe мы можем получить представление о внутренних деталях встроенных структур данных Go.
unsafe.Pointer
Указатель представляет адрес переменной в памяти. Он может преобразовать адрес любой переменной в тип указателя или преобразовать тип указателя в любой тип указателя. Это промежуточный тип между различными типами указателей. Сам указатель также является целым числом.
type Pointer int
Преобразование между разными типами в Go ограничено. Преобразование обычных базовых переменных в другие типы требует неглубокой копии в памяти, а прямое преобразование между типами переменных-указателей запрещено. Чтобы обойти это ограничение, пригодится unsafe.Pointer, который позволяет выполнять взаимное преобразование произвольных типов указателей.
Сложение и вычитание указателей
Хотя Pointer является целым числом, компилятор запрещает прямое сложение и вычитание. Если вы хотите выполнять операции, вам нужно преобразовать тип Pointer в тип uintptr для сложения и вычитания, а затем преобразовать uintptr в тип Pointer. uintptr на самом деле является целым числом.
type uintptr int
Давайте попробуем магию, которую мы только что узнали
package main
import "fmt"
import "unsafe"
type Rect struct {
Width int
Height int
}
func main() {
var r = Rect {50, 50}
// *Rect => Pointer => *int => int
var width = *(*int)(unsafe.Pointer(&r))
// *Rect => Pointer => uintptr => Pointer => *int => int
var height = *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&r)) + uintptr(8)))
fmt.Println(width, height)
}
------
50 50
Приведенный выше код использует небезопасный пакет для чтения содержимого структуры, который довольно громоздкий в форме. Обратите внимание на комментарии в коде. Читатель должен немного повернуть голову, чтобы понять вышеуказанный код. Далее мы пытаемся изменить значение структуры
package main
import "fmt"
import "unsafe"
type Rect struct {
Width int
Height int
}
func main() {
var r = Rect {50, 50}
// var pw *int
var pw = (*int)(unsafe.Pointer(&r))
// var ph *int
var ph = (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&r)) + uintptr(8)))
*pw = 100
*ph = 100
fmt.Println(r.Width, r.Height)
}
--------
100 100
uintptr(8) в коде очень неэлегантный, вы можете использовать метод Offsetof, предоставляемый unsafe, чтобы заменить его, который может напрямую получить смещение поля в структуре.
var ph = (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&r)) + unsafe.Offsetof(r.Height))
Вы можете жаловаться, почему манипулирование указателем такое громоздкое, не может ли оно быть проще? Разработчики языка Go специально разработали его таким образом, потому что манипулирование указателями очень небезопасно, поэтому оно должно создавать препятствия для пользователя.
Исследуйте внутренности среза
В разделе о слайсах мы знаем, что слайс разделен на две части: заголовок слайса и внутренний массив.Далее мы используем пакет unsafe для проверки внутренней структуры данных слайса, чтобы убедиться, что она такая же, как мы ожидал.
package main
import "fmt"
import "unsafe"
func main() {
// head = {address, 10, 10}
// body = [1,2,3,4,5,6,7,8,9,10]
var s = []int{1,2,3,4,5,6,7,8,9,10}
var address = (**[10]int)(unsafe.Pointer(&s))
var len = (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + uintptr(8)))
var cap = (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + uintptr(16)))
fmt.Println(address, *len, *cap)
var body = **address
for i:=0; i< len(body); i++ {
fmt.Printf("%d ", body[i])
}
}
------------------
0xc42000a080 10 10
1 2 3 4 5 6 7 8 9 10
Вывод именно такой, какой ожидает наша блокировка, но читатель должен хорошо подумать, почему address является переменной-указателем второго уровня.
Эффективное преобразование строк в байтовые срезы
В секции строки мы упомянули, что преобразование между ломтиками байтов и струнами требует копирования памяти. Если длина строки или ломтика байтов большой, преобразование будет иметь более высокую стоимость. Ниже мы предоставляем еще один эффективный метод преобразования через небезопасный пакет, что позволяет строку и байты ломтики до и после преобразования для совмещения внутреннего хранилища.
Разница между строками и байтовыми слайсами заключается в заголовке.Заголовок строки составляет 2 байта int, а заголовок слайса — 3 байта int.package main
import "fmt"
import "unsafe"
func main() {
fmt.Println(bytes2str(str2bytes("hello")))
}
func str2bytes(s string) []byte {
var strhead = *(*[2]int)(unsafe.Pointer(&s))
var slicehead [3]int
slicehead[0] = strhead[0]
slicehead[1] = strhead[1]
slicehead[2] = strhead[1]
return *(*[]byte)(unsafe.Pointer(&slicehead))
}
func bytes2str(bs []byte) string {
return *(*string)(unsafe.Pointer(&bs))
}
-----
hello
Помните, что срез байтов, преобразованный в эту форму, не должен изменяться, потому что его базовый массив байтов является общим, и модификация нарушит правило только для чтения строки. Во-вторых, строка или слайс, полученные в таком виде, могут использоваться только как временная локальная переменная, потому что общий массив байтов может быть переработан в любое время, а на память исходной строки или байтового слайса больше не ссылаются, вызывая переработку мусора. решил это.
Углубленное назначение переменных интерфейса
В разделе, посвященном переменным интерфейса, один из открытых вопросов: что происходит, когда переменные интерфейса назначаются?
Через небезопасный пакет мы можем увидеть детали внутри.Давайте назначим структурную переменную интерфейсной переменной, чтобы увидеть, повлияет ли изменение памяти структуры на память данных интерфейсной переменной.package main
import "fmt"
import "unsafe"
type Rect struct {
Width int
Height int
}
func main() {
var r = Rect{50, 50}
// {typeptr, dataptr}
var s interface{} = r
var sptrs = *(*[2]*Rect)(unsafe.Pointer(&s))
// var dataptr *Rect
var sdataptr = sptrs[1]
fmt.Println(sdataptr.Width, sdataptr.Height)
// 修改原对象,看看接口指向的对象是否受到影响
r.Width = 100
fmt.Println(sdataptr.Width, sdataptr.Height)
}
-------
50 50
50 50
Из вывода можно сделать вывод, что при присвоении переменной структуры переменной интерфейса память структуры будет скопирована. Если это присваивание между двумя переменными интерфейса, нужно ли будет также копировать указанные данные?
package main
import "fmt"
import "unsafe"
type Rect struct {
Width int
Height int
}
func main() {
// {typeptr, dataptr}
var s interface{} = Rect{50, 50}
var r = s
var rptrs = *(*[2]*Rect)(unsafe.Pointer(&r))
var rdataptr = rptrs[1]
var sptrs = *(*[2]*Rect)(unsafe.Pointer(&s))
var sdataptr = sptrs[1]
fmt.Println(sdataptr.Width, sdataptr.Height)
fmt.Println(rdataptr.Width, rdataptr.Height)
// 修改原对象
sdataptr.Width = 100
// 再对比一下原对象和目标对象
fmt.Println(sdataptr.Width, sdataptr.Height)
fmt.Println(rdataptr.Width, rdataptr.Height)
}
-----------
50 50
50 50
100 50
100 50
Из вывода видно, что память данных совместно используется двумя интерфейсными переменными до и после присваивания, и копирование данных не происходит. Далее мы представим третий вопрос, будет ли копироваться назначение различных типов переменных интерфейса?
package main
import "fmt"
import "unsafe"
type Areable interface {
Area() int
}
type Rect struct {
Width int
Height int
}
func (r Rect) Area() int {
return r.Width * r.Height
}
func main() {
// {typeptr, dataptr}
var s Areable = Rect{50, 50}
var r interface{} = s
var rptrs = *(*[2]*Rect)(unsafe.Pointer(&r))
var rdataptr = rptrs[1]
var sptrs = *(*[2]*Rect)(unsafe.Pointer(&s))
var sdataptr = sptrs[1]
fmt.Println(sdataptr.Width, sdataptr.Height)
fmt.Println(rdataptr.Width, rdataptr.Height)
// 修改原对象
sdataptr.Width = 100
// 再对比一下原对象和目标对象
fmt.Println(sdataptr.Width, sdataptr.Height)
fmt.Println(rdataptr.Width, rdataptr.Height)
}
------
50 50
50 50
100 50
100 50
В результате объекты данных, на которые указывают назначения между интерфейсами разных типов, по-прежнему являются общими. Далее мы вводим четвертый вопрос, происходит ли копирование памяти между типами интерфейса во время моделирования.
package main
import "fmt"
import "unsafe"
type Areable interface {
Area() int
}
type Rect struct {
Width int
Height int
}
func (r Rect) Area() int {
return r.Width * r.Height
}
func main() {
// {typeptr, dataptr}
var s interface{} = Rect{50, 50}
var r Areable = s.(Areable)
var rptrs = *(*[2]*Rect)(unsafe.Pointer(&r))
var rdataptr = rptrs[1]
var sptrs = *(*[2]*Rect)(unsafe.Pointer(&s))
var sdataptr = sptrs[1]
fmt.Println(sdataptr.Width, sdataptr.Height)
fmt.Println(rdataptr.Width, rdataptr.Height)
// 修改原对象
sdataptr.Width = 100
// 再对比一下原对象和目标对象
fmt.Println(sdataptr.Width, sdataptr.Height)
fmt.Println(rdataptr.Width, rdataptr.Height)
}
------
50 50
50 50
100 50
100 50
Ответ заключается в том, что данные моделирования по-прежнему используются совместно разными типами интерфейсов. Наконец, позвольте задать еще один вопрос: произойдет ли копирование памяти при приведении типа интерфейса к типу структуры?
package main
import "fmt"
import "unsafe"
type Areable interface {
Area() int
}
type Rect struct {
Width int
Height int
}
func (r Rect) Area() int {
return r.Width * r.Height
}
func main() {
// {typeptr, dataptr}
var s interface{} = Rect{50, 50}
var r Rect = s.(Rect)
var sptrs = *(*[2]*Rect)(unsafe.Pointer(&s))
var sdataptr = sptrs[1]
// 修改原对象
sdataptr.Width = 100
// 再对比一下原对象和目标对象
fmt.Println(sdataptr.Width, sdataptr.Height)
fmt.Println(r.Width, r.Height)
}
Ответ заключается в том, чтобы привести интерфейс к структурному типу, память будет скопирована, а данные между ними не будут разделены.
Из 5 вопросов выше мы можем сделать вывод, что типы интерфейсов и типы структур кажутся двумя разными мирами. Только присваивание и преобразование между типами интерфейса будут совместно использовать данные, а в других случаях данные будут копироваться.К другим случаям относятся присваивание между структурами, структура в интерфейс и интерфейс в структуру. Преобразование между различными переменными интерфейса, по сути, просто корректирует указатель типа внутри переменной интерфейса, а указатель данных не изменяется.
Мы также можем анализировать множество деталей через небезопасный пакет, В разделе расширенного контента мы будем часто использовать этот инструмент.
Прочитайте больше глав «Быстро изучите язык Go», нажмите и удерживайте изображение, чтобы определить QR-код, и подпишитесь на общедоступную учетную запись «Code Cave».