Notas de estudio de Go (74): inseguro de la biblioteca estándar de Go

GoEl lenguaje viene con unsafeel uso avanzado del paquete, como sugiere el nombre, unsafeno es seguro. GoDefinirlo 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 Golos 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

GoEs 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 Goidioma no permite la conversión de dos tipos de puntero.

Generalmente usamos *Tcomo tipo de puntero que representa un tipo de Tvariable de puntero que apunta . Por razones de seguridad, dos tipos de punteros diferentes no pueden convertir, como *intno 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 unsafela bolsa Pointera.

unsafe.PointerEs un puntero de especial significado, puede representar cualquier tipo de dirección, similar al Clenguaje del void*puntero, es versátil.

En circunstancias normales, *intno se puede convertir *float64, sino unsafe.Pointerhaciendo tránsito sobre él. En el siguiente ejemplo, mediante unsafe.Pointerla *intconversión a *float64, y nuevo *float64para 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.Pointereste puntero universal, podemos *Trealizar cualquier conversión. Entonces, ¿ unsafe.Pointerqué es al final? ¿Por qué otros tipos de indicadores se pueden convertir a unsafe.Pointerél? Eso depende unsafe.Pointerdel 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 Gonotas oficiales de idioma, ArbitraryTypepuede representar cualquier tipo (donde ArbitraryTypesolo se requieran los documentos, no debe preocuparse demasiado por ellos mismos, siempre que recuerde que puede representar cualquier tipo). Y unsafe.Pointernuevamente *ArbitraryType, que unsafe.Pointercualquier 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

uintptrTambié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 uintptrmáquina? Esto se debe a que unsafe.Pointerno es para la operación, no es compatible con la operación del operador + (más), pero uintptrpuede 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 structun ejemplo de estructuras de campo en el cuerpo, el uintptruso 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 personasignació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.

  1. Primero use newla función para declarar un *persontipo de variable de puntero p.

  2. Luego, *personel tipo de variables de puntero ppor unsafe.Pointerconversión al *stringtipo de variables de puntero pName.

  3. Debido a que personel primer campo de la estructura es el stringtipo Name, de modo pNameque el puntero apunta a Nameun campo (desplazamiento 0), pNamemodificar en realidad modifica el Namevalor del campo .

  4. Dado que el Agecampo no es personel primer campo, se debe realizar para modificar el cálculo del desplazamiento del puntero. Es necesario poner una variable de puntero ppor unsafe.Pointer convert uintptr, 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.Offsetofcalculado, la función devuelve un uintptrtipo de desplazamiento, este desplazamiento haría que el operador + pueda obtener el Agecampo de dirección de memoria correcto , es decir, por unsafe.Pointerel *inttipo 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.Pointerconversión al uintptrtipo de puntero. Después de completar la aritmética del puntero, pero también mediante unsafe.Pointerla 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).

  1. Con la Agevariable de puntero de campo de punto pAge, la asignación puede ser, modificar el Agevalor del campo de.

Este ejemplo es para explicar la uintptraritmé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 Goexisten tres tipos de punteros en el lenguaje, que son: común *T, unsafe.Pointery uintptr. A través de los ejemplos anteriores, se pueden resumir las reglas de conversión de estos tres:

  • Cualquier tipo *Tse puede convertir a unsafe.Pointer;
  • unsafe.PointerTambién se puede convertir a cualquier tipo *T;
  • unsafe.PointerSe puede convertir uintptr;
  • uintptrTambién se puede convertir a unsafe.Pointer;

Se puede encontrar que se unsafe.Pointerutiliza principalmente para la conversión de tipos de punteros y es un puente para la conversión de varios tipos de punteros. uintptrSe utiliza principalmente para la aritmética de punteros, especialmente para localizar diferentes memorias por desplazamiento.

4. inseguro.

SizeofLa 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 booltipo de un byte, int8también es un byte.

Por SizeofPuede 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 int8un 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 nrepresentan bit, int8representan 8bit, int16representan 16bit, etc.

Para plataformas y inttipos 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á inty int64el tamaño es el mismo, porque yo uso las plataformas de computadora de 64 bits.

Consejo: una structhuella 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
unsafebolsa es el Pointerindicador más comúnmente utilizado por el cual se puede hacer *T, uintptry Pointerentre los tres convertidores con el fin de lograr sus necesidades, como copia de memoria cero o por uintptrrealizar aritmética de puntero, lo que puede mejorar la eficiencia del programa.

unsafeAunque 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 Gocompilador 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 []bytesu vez string, puede pasar unsafe.Pointercopia 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 uintptrpara la aritmética del puntero, GCno uintptrcuando el puntero, que uintptrno puede contener el objeto, uintptrse reciclará el tipo de objetivo;
  • unsafe.Pointer Se puede convertir con punteros ordinarios;
  • unsafe.PointerSe pueden uintptrconvertir 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 un wpuntero al valor inicial;
  • unsafe.Offsetof(w.b)Obtención de bla variable de compensación;
  • Los dos sumados al bvalor de dirección obtenido , el puntero de propósito general Pointerse 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ón int, y finalmente reasignación a una variable 10, de modo que la aritmética del puntero completado.

Supongo que te gusta

Origin blog.csdn.net/wohu1104/article/details/113795624
Recomendado
Clasificación