unsafe
Dirección de la documentación oficial: unsafe package - unsafe - Go Packages
La biblioteca estándar unsafe es una biblioteca proporcionada oficialmente que permite la programación de bajo nivel. Las operaciones proporcionadas por este paquete pueden omitir el sistema de tipos de Go para leer y escribir memoria directamente. Este paquete puede no ser portable, y la oficina afirma que este paquete no está protegido por las pautas de compatibilidad de Go 1. A pesar de esto, unsafe también es utilizado por una gran cantidad de proyectos, incluida la biblioteca estándar proporcionada oficialmente.
TIP
La razón por la que no es portable es que algunos resultados dependen de la implementación del sistema operativo, y diferentes sistemas pueden tener diferentes resultados.
ArbitraryType
type ArbitraryType intArbitrary se puede traducir como arbitrario, y aquí representa cualquier tipo, y no es equivalente a any. De hecho, este tipo no pertenece al paquete unsafe, y aparece aquí solo para propósitos de documentación.
IntegerType
type IntegerType intIntegerType representa cualquier tipo de entero. De hecho, este tipo no pertenece al paquete unsafe, y aparece aquí solo para propósitos de documentación.
No es necesario preocuparse demasiado por estos dos tipos anteriores, solo son una representación. Al usar las funciones del paquete unsafe, el editor incluso le indicará que el tipo no coincide. Su tipo real es el tipo específico que ingresó.
Sizeof
func Sizeof(x ArbitraryType) uintptrDevuelve el tamaño de la variable x en bytes, sin incluir el tamaño de su contenido referenciado. Por ejemplo:
func main() {
var ints byte = 1
fmt.Println(unsafe.Sizeof(ints))
var floats float32 = 1.0
fmt.Println(unsafe.Sizeof(floats))
var complexs complex128 = 1 + 2i
fmt.Println(unsafe.Sizeof(complexs))
var slice []int = make([]int, 100)
fmt.Println(unsafe.Sizeof(slice))
var mp map[string]int = make(map[string]int, 0)
fmt.Println(unsafe.Sizeof(mp))
type person struct {
name string
age int
}
fmt.Println(unsafe.Sizeof(person{}))
type man struct {
name string
}
fmt.Println(unsafe.Sizeof(man{}))
}1
4
16
24
8
24
16Offsetof
func Offsetof(x ArbitraryType) uintptrEsta función se usa para representar el desplazamiento de campo dentro de una estructura, por lo que x debe ser un campo de estructura, o el valor devuelto es el número de bytes entre el inicio de la dirección de la estructura y el inicio de la dirección del campo. Por ejemplo
func main() {
type person struct {
name string
age int
}
p := person{
name: "aa",
age: 11,
}
fmt.Println(unsafe.Sizeof(p))
fmt.Println(unsafe.Offsetof(p.name))
fmt.Println(unsafe.Sizeof(p.name))
fmt.Println(unsafe.Offsetof(p.age))
fmt.Println(unsafe.Sizeof(p.age))
}24
0
16
16
8Alignof
Si no entiende qué es la alineación de memoria, puede ir a: Alineación de memoria en Go explicada en detalle - Juejin (juejin.cn)
func Alignof(x ArbitraryType) uintptrEl tamaño de alineación generalmente es el mínimo entre la longitud de palabra de la computadora en bytes y Sizeof. Por ejemplo, en una máquina amd64, la longitud de palabra es de 64 bits, es decir, 8 bytes. Por ejemplo:
func main() {
type person struct {
name string
age int32
}
p := person{
name: "aa",
age: 11,
}
fmt.Println(unsafe.Alignof(p), unsafe.Sizeof(p))
fmt.Println(unsafe.Alignof(p.name), unsafe.Sizeof(p.name))
fmt.Println(unsafe.Alignof(p.age), unsafe.Sizeof(p.age))
}8 24
8 16
4 4Pointer
type Pointer *ArbitraryTypePointer es un "puntero" que puede apuntar a cualquier tipo. Su tipo es *ArbitraryType. Este tipo se combina con uintptr para aprovechar realmente el poder del paquete unsafe. En la descripción de la documentación oficial, el tipo unsafe.Pointer puede realizar cuatro operaciones especiales, que son:
- Cualquier tipo de puntero se puede convertir a
unsafe.Pointer unsafe.Pointerse puede convertir a cualquier tipo de punterouintptrse puede convertir aunsafe.Pointerunsafe.Pointerse puede convertir auintptr
Estas cuatro operaciones especiales constituyen la piedra angular de todo el paquete unsafe, y también son estas cuatro operaciones las que permiten escribir código que puede omitir el sistema de tipos y leer y escribir memoria directamente. Se recomienda tener especial cuidado al usarlas.
TIP
unsafe.Pointer no se puede desreferenciar, y tampoco se puede tomar la dirección.
(1) Convertir *T1 a unsafe.Pointer y luego a *T2
Existentes los tipos *T1 y *T2, asumiendo que T2 no es mayor que T1 y ambos tienen una disposición de memoria equivalente, se permite convertir datos de tipo T2 a T1. Por ejemplo:
func main() {
fmt.Println(Float64bits(12.3))
fmt.Println(Float64frombits(Float64bits(12.3)))
}
func Float64bits(f float64) uint64 {
return *(*uint64)(unsafe.Pointer(&f))
}
func Float64frombits(b uint64) float64 {
return *(*float64)(unsafe.Pointer(&b))
}4623113902481840538
12.3Estas dos funciones son en realidad dos funciones del paquete math. Los cambios de tipo en el proceso son los siguientes
float64 -> *float64 -> unsafe.Pointer -> *uint64 -> uint64 -> *uint64 -> unsafe.Pointer -> *float64 -> float64(2) Convertir unsafe.Pointer a uintptr
Al convertir unsafe.Pointer a uintptr, la dirección a la que apunta el primero se usará como el valor del segundo. uintptr guarda la dirección. La diferencia es que el primero es sintácticamente un puntero, una referencia, mientras que el segundo es solo un valor entero. Por ejemplo
func main() {
num := 1
fmt.Println(unsafe.Pointer(&num))
fmt.Printf("0x%x", uintptr(unsafe.Pointer(&num)))
}0xc00001c088
0xc00001c088Una diferencia mayor es el manejo de la recolección de basura. Dado que unsafe.Pointer es una referencia, no se recolectará cuando sea necesario, mientras que el segundo es solo un valor y, naturalmente, no tendrá este tratamiento especial. Otro punto a tener en cuenta es que cuando la dirección del elemento al que apunta el puntero se mueve, el GC actualizará la dirección antigua a la que apunta el puntero, pero no actualizará el valor guardado por uintptr. Por ejemplo, el siguiente código puede tener problemas:
func main() {
num := 16
address := uintptr(unsafe.Pointer(&num))
np := (*int64)(unsafe.Pointer(address))
fmt.Println(*np)
}En algunos casos, después de que el GC mueve la variable, la dirección a la que apunta address ya no es válida. En este momento, usar ese valor para crear un puntero causará un panic
panic: runtime error: invalid memory address or nil pointer dereferencePor lo tanto, no se recomienda guardar el valor convertido de Pointer a uintptr.
(3) Convertir uintptr a unsafe.Pointer
La siguiente manera puede obtener un puntero a través de uintptr. Siempre que el puntero sea válido, no habrá una situación de dirección inválida como en el ejemplo dos. Pointer y el puntero de tipo en sí no admiten aritmética de punteros, pero uintptr es solo un valor entero y se pueden realizar operaciones matemáticas. Realizar operaciones matemáticas en uintptr y luego convertirlo a Pointer puede completar la aritmética de punteros.
p = unsafe.Pointer(uintptr(p) + offset)De esta manera, se puede acceder a algunos elementos internos de algunos tipos solo a través de un puntero, como matrices y estructuras, independientemente de si sus elementos internos están expuestos externamente. Por ejemplo
func main() {
type person struct {
name string
age int32
}
p := &person{"jack", 18}
pp := unsafe.Pointer(p)
fmt.Println(*(*string)(unsafe.Pointer(uintptr(pp) + unsafe.Offsetof(p.name))))
fmt.Println(*(*int32)(unsafe.Pointer(uintptr(pp) + unsafe.Offsetof(p.age))))
s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
ps := unsafe.Pointer(&s[0])
fmt.Println(*(*int)(unsafe.Pointer(uintptr(ps) + 8)))
fmt.Println(*(*int)(unsafe.Pointer(uintptr(ps) + 16)))
}jack
18
2Add
func Add(ptr Pointer, len IntegerType) PointerAdd devolverá el Pointer actualizado con el desplazamiento len, equivalente a Pointer(uintptr(ptr) + uintptr(len))
Pointer(uintptr(ptr) + uintptr(len))Por ejemplo:
func main() {
s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
ps := unsafe.Pointer(&s[0])
fmt.Println(*(*int)(unsafe.Add(ps, 8)))
fmt.Println(*(*int)(unsafe.Add(ps, 16)))
}2
3SliceData
func SliceData(slice []ArbitraryType) *ArbitraryTypeEsta función recibe un slice y devuelve la dirección inicial de su matriz subyacente. Si no usa SliceData, solo puede obtener la dirección de la matriz subyacente tomando el puntero de su primer elemento, como se muestra a continuación
func main() {
nums := []int{1, 2, 3, 4}
for p, i := unsafe.Pointer(&nums[0]), 0; i < len(nums); p, i = unsafe.Add(p, unsafe.Sizeof(nums[0])), i+1 {
num := *(*int)(p)
fmt.Println(num)
}
}Por supuesto, también se puede obtener a través del tipo reflect.SliceHeader, pero está obsoleto desde la versión 1.20. SliceData es para reemplazarlo. El ejemplo de uso de SliceData es el siguiente
func main() {
nums := []int{1, 2, 3, 4}
for p, i := unsafe.Pointer(unsafe.SliceData(nums)), 0; i < len(nums); p, i = unsafe.Add(p, unsafe.Sizeof(int(0))), i+1 {
num := *(*int)(p)
fmt.Println(num)
}
}Slice
func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryTypeLa función Slice recibe un puntero y un desplazamiento de longitud. Devolverá la forma de expresión de slice de ese segmento de memoria. El proceso no implicará copia de memoria. Modificar el slice afectará directamente los datos en esa dirección, y viceversa. Generalmente se usa junto con SliceData.
func main() {
nums := []int{1, 2, 3, 4}
numsRef1 := unsafe.Slice(unsafe.SliceData(nums), len(nums))
numsRef1[0] = 2
fmt.Println(nums)
}[2 2 3 4]Modificar los datos del slice numsRef1 hará que los datos de nums también cambien
StringData
func StringData(str string) *byteIgual que la función SliceData, pero debido a que la conversión de cadena a slice de bytes es frecuente, se toma por separado. El ejemplo de uso es el siguiente
func main() {
str := "hello,world!"
for ptr, i := unsafe.Pointer(unsafe.StringData(str)), 0; i < len(str); ptr, i = unsafe.Add(ptr, unsafe.Sizeof(byte(0))), i+1 {
char := *(*byte)(ptr)
fmt.Println(string(char))
}
}Dado que los literales de cadena se almacenan en el segmento de solo lectura del proceso, si intenta modificar los datos subyacentes de la cadena aquí, el programa fallará directamente con un fatal. Sin embargo, para las variables de cadena almacenadas en la pila, es completamente factible modificar sus datos subyacentes en tiempo de ejecución.
String
func String(ptr *byte, len IntegerType) stringIgual que la función Slice, recibe un puntero de tipo byte y su desplazamiento de longitud, y devuelve su forma de expresión de cadena. El proceso no implica copia de memoria. A continuación se muestra un ejemplo de conversión de slice de bytes a cadena
func main() {
bytes := []byte("hello world")
str := unsafe.String(unsafe.SliceData(bytes), len(bytes))
fmt.Println(str)
}StringData y String no implican copia de memoria en el proceso de conversión entre cadenas y slices de bytes. El rendimiento es mejor que la conversión de tipo directa, pero solo es aplicable en casos de solo lectura. Si planea modificar los datos, es mejor no usar esto.
