Go
El lenguaje viene con unsafe
el uso avanzado del paquete, como sugiere el nombre, unsafe
no es seguro. Go
Definirlo como el nombre del paquete también nos permite no usarlo tanto como sea posible. Pero si bien es inseguro, también tiene la ventaja de que puede eludir Go
los mecanismos de seguridad de la memoria, leer y escribir en memoria directa. Por lo tanto, a veces, por necesidades de rendimiento, sigue siendo arriesgado usarlo para operar en la memoria.
1. Conversión de tipo de puntero
Go
Es un lenguaje estático fuertemente tipado. El tipo fuerte significa que una vez definido, el tipo no se puede cambiar; estático significa que la verificación de tipo se realiza antes de la ejecución. Al mismo tiempo, por razones de seguridad, el Go
idioma no permite la conversión de dos tipos de puntero.
Generalmente usamos *T
como tipo de puntero que representa un tipo de T
variable de puntero que apunta . Por razones de seguridad, dos tipos de punteros diferentes no pueden convertir, como *int
no girar *float64
.
Veamos el siguiente código:
func main() {
i:= 10
ip:=&i
var fp *float64 = (*float64)(ip)
fmt.Println(fp)
}
Cuando se compile este código, se le pedirá
cannot convert ip (type * int) to type * float64
Es decir, no se puede llevar a cabo una transformación forzada. ¿Qué pasa si todavía necesita ser convertido? Esto requiere el uso de unsafe
la bolsa Pointer
a.
unsafe.Pointer
Es un puntero de especial significado, puede representar cualquier tipo de dirección, similar al C
lenguaje del void*
puntero, es versátil.
En circunstancias normales, *int
no se puede convertir *float64
, sino unsafe.Pointer
haciendo tránsito sobre él. En el siguiente ejemplo, mediante unsafe.Pointer
la *int
conversión a *float64
, y nuevo *float64
para la operación de multiplicación por 3, encontrará que el valor original de la variable i también cambia, se convierte en 30.
func main() {
i := 10
ip := &i
var fp *float64 = (*float64)(unsafe.Pointer(ip))
*fp = *fp * 3
fmt.Println(*ip) // 30
}
Ilustrado por unsafe.Pointer
este puntero universal, podemos *T
realizar cualquier conversión. Entonces, ¿ unsafe.Pointer
qué es al final? ¿Por qué otros tipos de indicadores se pueden convertir a unsafe.Pointer
él? Eso depende unsafe.Pointer
del código fuente que se define de la siguiente manera:
// ArbitraryType is here for the purposes of documentation
// only and is not actually part of the unsafe package.
// It represents the type of an arbitrary Go expression.
type ArbitraryType int
type Pointer *ArbitraryType
Por Go
notas oficiales de idioma, ArbitraryType
puede representar cualquier tipo (donde ArbitraryType
solo se requieran los documentos, no debe preocuparse demasiado por ellos mismos, siempre que recuerde que puede representar cualquier tipo). Y unsafe.Pointer
nuevamente *ArbitraryType
, que unsafe.Pointer
cualquier tipo de puntero, que es un tipo común de puntero, es suficiente para representar cualquier dirección de memoria.
2. tipo de puntero uintptr
uintptr
También es un tipo de puntero, que es lo suficientemente grande para representar cualquier puntero. Su definición de tipo es la siguiente:
// uintptr is an integer type that is large enough
// to hold the bit pattern of any pointer.
type uintptr uintptr
Ahora que lo tiene unsafe.Pointer
, ¿por qué diseñarlo a uintptr
máquina? Esto se debe a que unsafe.Pointer
no es para la operación, no es compatible con la operación del operador + (más), pero uintptr
puede serlo. A través de él, se puede calcular el desplazamiento del puntero, de modo que se puede acceder a una memoria específica, y se puede lograr el propósito de leer y escribir en una memoria específica.Esta es una operación real a nivel de memoria.
En el siguiente código, el puntero se desplaza modificando struct
un ejemplo de estructuras de campo en el cuerpo, el uintptr
uso de demostración .
func main() {
p := new(person)
//Name是person的第一个字段不用偏移,即可通过指针修改
pName := (*string)(unsafe.Pointer(p))
*pName = "wohu"
//Age并不是person的第一个字段,所以需要进行偏移,这样才能正确定位到Age字段这块内存,才可以正确的修改
pAge := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + unsafe.Offsetof(p.Age)))
*pAge = 20
fmt.Printf("p is %#v", *p) // p is main.person{Name:"wohu", Age:20}
}
type person struct {
Name string
Age int
}
Este ejemplo no es para acceder directamente a la person
asignación de campo de estructura de campo correspondiente, sino compensar por un puntero para encontrar la memoria correspondiente y la operación de asignación de memoria.
Los pasos de la operación se describen en detalle a continuación.
-
Primero use
new
la función para declarar un*person
tipo de variable de punterop
. -
Luego,
*person
el tipo de variables de punterop
porunsafe.Pointer
conversión al*string
tipo de variables de punteropName
. -
Debido a que
person
el primer campo de la estructura es elstring
tipoName
, de modopName
que el puntero apunta aName
un campo (desplazamiento 0),pName
modificar en realidad modifica elName
valor del campo . -
Dado que el
Age
campo no esperson
el primer campo, se debe realizar para modificar el cálculo del desplazamiento del puntero. Es necesario poner una variable de punterop
porunsafe.Pointe
r convertuintptr
, para abordar la aritmética.
Dado que se va a realizar el desplazamiento del puntero, ¿cuánto debería desplazarse? Este desplazamiento puede funcionar unsafe.Offsetof
calculado, la función devuelve un uintptr
tipo de desplazamiento, este desplazamiento haría que el operador + pueda obtener el Age
campo de dirección de memoria correcto , es decir, por unsafe.Pointer
el *int
tipo de variable de puntero convertido pAge
.
Tenga en cuenta que entonces, si la aritmética del puntero se va a realizar, primero mediante la unsafe.Pointer
conversión al uintptr
tipo de puntero. Después de completar la aritmética del puntero, pero también mediante unsafe.Pointer
la conversión a un tipo de puntero real (como en el ejemplo *int
, este valor se puede asignar u operar en este tipo de memoria).
- Con la
Age
variable de puntero de campo de puntopAge
, la asignación puede ser, modificar elAge
valor del campo de.
Este ejemplo es para explicar la uintptr
aritmética del puntero principal , por lo que la asignación que se escribe en una estructura de campo es tan compleja, de acuerdo con la codificación normal, el código de ejemplo anterior y el siguiente código como resultado.
func main() {
p :=new(person)
p.Name = "wohu"
p.Age = 20
fmt.Println(*p)
}
El núcleo de la aritmética de punteros es que opera en cada dirección de memoria.A través del aumento o disminución de la dirección de memoria, puede apuntar a diferentes piezas de memoria y operar sobre ellas, y no necesita saber qué nombre (nombre de variable ) se da este fragmento de memoria.
3. Reglas de conversión de punteros
Usted ya sabe Go
existen tres tipos de punteros en el lenguaje, que son: común *T
, unsafe.Pointer
y uintptr
. A través de los ejemplos anteriores, se pueden resumir las reglas de conversión de estos tres:
- Cualquier tipo
*T
se puede convertir aunsafe.Pointer
; unsafe.Pointer
También se puede convertir a cualquier tipo*T
;unsafe.Pointer
Se puede convertiruintptr
;uintptr
También se puede convertir aunsafe.Pointer
;
Se puede encontrar que se unsafe.Pointer
utiliza principalmente para la conversión de tipos de punteros y es un puente para la conversión de varios tipos de punteros. uintptr
Se utiliza principalmente para la aritmética de punteros, especialmente para localizar diferentes memorias por desplazamiento.
4. inseguro.
Sizeof
La función devuelve el tamaño de la memoria ocupada por un tipo, y solo el tipo de este tamaño, independientemente del tipo y contenido correspondiente al tamaño de la variable de almacenamiento, como el bool
tipo de un byte, int8
también es un byte.
Por Sizeof
Puede ver la función de cualquier tipo (como cadenas, sector, entero) tamaño de memoria, el siguiente código de muestra:
func main() {
fmt.Println(unsafe.Sizeof(true)) // 1
fmt.Println(unsafe.Sizeof(int8(0))) // 1
fmt.Println(unsafe.Sizeof(int16(0))) // 2
fmt.Println(unsafe.Sizeof(int32(0))) // 4
fmt.Println(unsafe.Sizeof(int64(0))) // 8
fmt.Println(unsafe.Sizeof(int(0))) // 8
fmt.Println(unsafe.Sizeof(string("张三"))) // 16
fmt.Println(unsafe.Sizeof([]string{
"李四", "张三"})) // 24
}
Para entero, el número de bytes ocupados significa que el tamaño de este tipo de alcance de almacenamiento digital, como int8
un byte, es decir 8bit
, para que pueda almacenar el rango de tamaño es -128~~127
, es decir , −2^(n-1)
a 2^(n-1)−1
. Que n
representan bit
, int8
representan 8bit
, int16
representan 16bit
, etc.
Para plataformas y int
tipos relacionados , dependiendo de la plataforma de 32 bits o 64 bits, tomará la más grande. Por ejemplo, soy dueño de las pruebas anteriores a la salida, encontrará int
y int64
el tamaño es el mismo, porque yo uso las plataformas de computadora de 64 bits.
Consejo: una struct
huella del tamaño de la estructura, igual al contenido en el tipo de campo y el tamaño de la huella de memoria.
Resumen: La
unsafe
bolsa es el Pointer
indicador más comúnmente utilizado por el cual se puede hacer *T
, uintptr
y Pointer
entre los tres convertidores con el fin de lograr sus necesidades, como copia de memoria cero o por uintptr
realizar aritmética de puntero, lo que puede mejorar la eficiencia del programa.
unsafe
Aunque las funciones del paquete no son seguras, son realmente fragantes, como la aritmética de punteros, la conversión de tipos, etc., lo que puede ayudarnos a mejorar el rendimiento. Sin embargo, recomiendo no usar tanto como sea posible, ya que puede pasar por alto el Go
compilador de lenguaje de verificación, puede encontrar problemas debido a que se producen errores operativos. Por supuesto, si es necesario para mejorar el rendimiento operativo se requiere, o se puede utilizar, como a []byte
su vez string
, puede pasar unsafe.Pointer
copia de memoria cero.
5. La diferencia entre uintptr y unsafe.Pointer
unsafe.Pointer
Es simplemente un tipo de puntero general simple, utilizado para convertir diferentes tipos de punteros, y no puede participar en operaciones de puntero;- Y
uintptr
para la aritmética del puntero,GC
nouintptr
cuando el puntero, queuintptr
no puede contener el objeto,uintptr
se reciclará el tipo de objetivo; unsafe.Pointer
Se puede convertir con punteros ordinarios;unsafe.Pointer
Se puedenuintptr
convertir entre sí;
package main
import (
"fmt"
"unsafe"
)
type W struct {
b int32
c int64
}
func main() {
var w *W = new(W)
//这时w的变量打印出来都是默认值0,0
fmt.Println(w.b, w.c)
//现在我们通过指针运算给b变量赋值为10
b := unsafe.Pointer(uintptr(unsafe.Pointer(w)) + unsafe.Offsetof(w.b))
*((*int)(b)) = 10
//此时结果就变成了10,0
fmt.Println(w.b, w.c)
}
uintptr(unsafe.Pointer(w))
Obtenga unw
puntero al valor inicial;unsafe.Offsetof(w.b)
Obtención deb
la variable de compensación;- Los dos sumados al
b
valor de dirección obtenido , el puntero de propósito generalPointer
se convierte en un puntero particular((*int)(b))
, por*
el valor del símbolo, luego la asignación.*((*int)(b))
Correspondiente a la(*int)(b)
conversiónint
, y finalmente reasignación a una variable 10, de modo que la aritmética del puntero completado.