Storia della questione:
Gli slice sono una delle strutture dinamiche chiave in Go, nate come alternativa agli array di lunghezza fissa per aumentare la comodità e la redditività della memoria. Forniscono una gestione flessibile dei sottoinsiemi degli array, ma presentano una serie di sottigliezze importanti per un codice performante e sicuro.
Problema:
Molti sviluppatori non comprendono come siano strutturati gli slice: uno slice non è l'array stesso, ma una struttura con un puntatore all'array, la lunghezza e la capacità. Questo può portare a perdite di memoria, bug quando si lavora con copie e effetti inaspettati quando si modifica l'array originale.
Soluzione:
Uno slice è un tipo:
type slice struct { ptr unsafe.Pointer len int cap int }
Quando si espande uno slice usando append(), potrebbe verificarsi una riallocazione dell'array di supporto, e tutti i precedenti riferimenti al vecchio array rimarranno validi, ma faranno riferimento ai vecchi dati. Ignorare questa caratteristica porta a errori e memory leak.
Esempio di allocazione di memoria corretta e copia:
src := []int{1,2,3,4,5} dst := make([]int, len(src)) copy(dst, src)
Uno slice creato utilizzando [:] condivide l'array sottostante, e le loro modifiche si influenzano a vicenda se non viene eseguita la copia.
Caratteristiche chiave:
Cosa succede quando si aumenta uno slice tramite append superando la cap, se altri slice hanno riferimenti allo stesso array?
append superando la cap crea un array sottostante con una nuova allocazione in memoria, e solo questo slice farà riferimento al nuovo array, mentre gli altri faranno riferimento al vecchio. Questa è una causa comune di divergenza dei dati.
Perché è importante non conservare slice a vita lunga di dimensioni contenute, ottenute da un grande array?
Anche se lo slice è molto piccolo, il suo puntatore conserva un riferimento all'intero array di supporto, il che può portare a mantenere un grande array in memoria e memory leak.
Cosa succede se si slice di un array oltre i suoi limiti?
Si verifica un panic: errore di runtime: limiti dello slice superati.
La funzione legge un grande file in un array di byte e restituisce uno slice dei primi 100 elementi. Questo slice viene poi mantenuto a lungo, ma tutta la memoria per il grande array rimane nel GC.
Pro:
Contro:
Subito dopo aver ottenuto lo slice, viene eseguita la copia della sezione necessaria in un nuovo slice con make e copy. Il vecchio array viene immediatamente dimenticato e il GC libera la memoria.
Pro:
Contro: