Využití standardního balíčku „unsafe“ v jazyku Go

2. 12. 2025
Doba čtení: 36 minut

Sdílet

Autor: Go Lang
V dnešní části dnes již dosti nepravidelně vycházejícího seriálu o jazyku Go si popíšeme možnosti nabízené standardním balíčkem nazvaným „unsafe“. I přes poněkud nebezpečně znějící název obsahuje užitečné funkce.

Obsah

1. Využití standardního balíčku „unsafe“ v programovacím jazyku Go

2. Nápověda dostupná pro balíček „unsafe“

3. Funkce unsafe.Sizeof

4. Příklady použití funkce unsafe.Sizeof

5. unsafe.Sizeof a složitější datové struktury

6. Velikost hodnot nil různého typu

7. Vliv pořadí prvků ve struktuře na její celkovou velikost

8. Vyhodnocení unsafe.Sizeof na konstantu

9. Funkce unsafe.Alignof

10. Zarovnání u základních datových typů i u struktur

11. Vliv typů prvků struktury na její celkové zarovnání

12. Vyhodnocení unsafe.Alignof na konstantu

13. Funkce unsafe.Offsetof

14. Přečtení offsetů jednotlivých prvků datových struktur

15. Offsety prvků ve strukturách obsahujících výplňové bajty

16. Datový typ unsafe.Pointer

17. Explicitní (ruční) alokace a dealokace paměti

18. Unsafe ukazatele jsou skutečně unsafe

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Využití standardního balíčku „unsafe“ v programovacím jazyku Go

V seriálu o programovacím jazyku Go, který zde na Rootu (dnes již spíše nepravidelně) vychází, jsme si, pochopitelně kromě mnoha dalších věcí, popsali i mnoho balíčků patřících do standardní knihovny, tj. balíčků, které není zapotřebí samostatně instalovat a které tak mohou být součástí každé aplikace vytvořené v jazyku Go (existují totiž i poměrně rozsáhlé aplikace, které si kupodivu vystačí „pouze“ se základní knihovnou tohoto jazyka). Už několikrát jsme se zmínili i o standardním balíčku nazvaném unsafe. Typicky jsme se s tímto balíčkem setkali v souvislosti s voláním nativního céčkového kódu, zejména při práci s céčkovými ukazateli. V dnešním článku si možnosti nabízené tímto balíčkem popíšeme poněkud podrobněji, pochopitelně i s uvedením demonstračních příkladů.

Poznámka: jak již název balíčku unsafe naznačuje nebo dokonce varuje, může neopatrné použití funkcí z unsafe vést k pádům aplikace, například při přístupu k neinicializované paměti atd. Taktéž je vhodné mít na paměti, že některé operace budou plně funkční pouze na některých architekturách nebo na některých operačních systémech.

2. Nápověda dostupná pro balíček „unsafe“

Vzhledem k tomu, že je unsafe standardním balíčkem, je snadné získat nápovědu s jeho popisem a později i nápovědu s popisem jednotlivých datových typů a funkcí, které jsou v něm implementovány. Pokud máte nainstalovány základní nástroje jazyka Go, postačuje pro získání nápovědy napsat do příkazového řádku následující příkaz:

$ go doc unsafe

Povšimněte si, jak je tento balíček ve skutečnosti „malý“ – obsahuje totiž pouze pět veřejně dostupných (viditelných) funkcí a tři veřejné datové typy (dokonce ani neobsahuje žádné definice konstant):

package unsafe // import "unsafe"
 
Package unsafe contains operations that step around the type safety of Go
programs.
 
Packages that import unsafe may be non-portable and are not protected by the Go
1 compatibility guidelines.
 
func Alignof(x ArbitraryType) uintptr
func Offsetof(x ArbitraryType) uintptr
func Sizeof(x ArbitraryType) uintptr
func String(ptr *byte, len IntegerType) string
func StringData(str string) *byte
type ArbitraryType int
    func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType
    func SliceData(slice []ArbitraryType) *ArbitraryType
type IntegerType int
type Pointer *ArbitraryType
    func Add(ptr Pointer, len IntegerType) Pointer
Poznámka: pochopitelně stále platí, že všechny veřejně dostupné symboly (v tomto případě se jedná o jména funkcí a typů) jsou v balíčku unsafe mají na začátku svého názvu velké písmeno; jinak by tyto symboly nebyly mimo svůj balíček dostupné.

Programátoři, co nesnášíte BS, ale máte rádi business! Y Soft je česká firma s globálním dopadem (100+ zemí, 1M+ uživatelů a >100% meziroční růst). R&D úplně bez manažerů (130 developerů). Otevíráme 30 pozic pro Cloud a AI: Praha/Brno/Ostrava/remote. Zodpovědnost ano, mikro-management ne. Pojď někam, kde můžeš věci změnit.

Y Soft logo

3. Funkce unsafe.Sizeof

První funkcí z balíčku unsafe, se kterou se v dnešním článku alespoň ve stručnosti seznámíme, je funkce nazvaná Sizeof. Tato funkce umožňuje zjistit velikost paměti, kterou je nutné rezervovat pro uložení výsledku nějakého výrazu. Ovšem je nutné si dát pozor na to, že se nejedná o přímou obdobu operátoru sizeof známého z programovacího jazyka C, protože unsafe.Sizeof pracuje přímo s hodnotami a nikoli se zápisem typu.

Zobrazení nápovědy k této funkci je snadné:

$ go doc unsafe.Sizeof

Zobrazit by se měly následující informace, které si v rámci dalších kapitol ověříme:

package unsafe // import "unsafe"
 
func Sizeof(x ArbitraryType) uintptr
    Sizeof takes an expression x of any type and returns the size in bytes of a
    hypothetical variable v as if v was declared via var v = x. The size does
    not include any memory possibly referenced by x. For instance, if x is a
    slice, Sizeof returns the size of the slice descriptor, not the size of the
    memory referenced by the slice; if x is an interface, Sizeof returns the
    size of the interface value itself, not the size of the value stored in the
    interface. For a struct, the size includes any padding introduced by field
    alignment. The return value of Sizeof is a Go constant if the type of the
    argument x does not have variable size. (A type has variable size if it is a
    type parameter or if it is an array or struct type with elements of variable
    size).
Poznámka: zajímavé je, že výsledkem této funkce je hodnota typu uintptr, což by mohlo značit, že se jedná o ukazatel. Ve skutečnosti tomu tak není a na uintptr se můžeme spíše dívat jako na rozdíl mezi dvěma ukazateli – taková hodnota musí mít rozsah odpovídající platformě (na 32bitových platformách nemá smysl mít velikost/rozsah 64 bitový a naopak na 64bitových platformách nemusí 32 bitů dostačovat). Rozsah hodnot typu uintptr se odlišuje podle použité architektury, tj. má buď šířku 32 bitů nebo 64 bitů.

4. Příklady použití funkce unsafe.Sizeof

Vyzkoušejme si nyní základní způsoby použití funkce unsafe.Sizeof. Nejdříve si necháme vypsat velikosti numerických hodnot různých typů (jedná se skutečně o hodnoty, které pouze konvertujeme tak, abychom získali hodnotu kýženého typu):

package main
 
import (
        "fmt"
        "unsafe"
)
 
func main() {
        fmt.Printf("sizeof int8       = %d byte(s)\n", unsafe.Sizeof(int8(0)))
        fmt.Printf("sizeof int16      = %d byte(s)\n", unsafe.Sizeof(int16(0)))
        fmt.Printf("sizeof int32      = %d byte(s)\n", unsafe.Sizeof(int32(0)))
        fmt.Printf("sizeof int64      = %d byte(s)\n", unsafe.Sizeof(int64(0)))
        fmt.Printf("sizeof int        = %d byte(s)\n", unsafe.Sizeof(int(0)))
        fmt.Printf("sizeof float32    = %d byte(s)\n", unsafe.Sizeof(float32(0)))
        fmt.Printf("sizeof float64    = %d byte(s)\n", unsafe.Sizeof(float64(0)))
        fmt.Printf("sizeof complex64  = %d byte(s)\n", unsafe.Sizeof(complex64(0)))
        fmt.Printf("sizeof complex128 = %d byte(s)\n", unsafe.Sizeof(complex128(0)))
        fmt.Printf("sizeof uintptr    = %d byte(s)\n", unsafe.Sizeof(uintptr(0)))
}

Výsledky mohou na 64bitové platformě vypadat následovně:

sizeof int8       = 1 byte(s)
sizeof int16      = 2 byte(s)
sizeof int32      = 4 byte(s)
sizeof int64      = 8 byte(s)
sizeof int        = 8 byte(s)
sizeof float32    = 4 byte(s)
sizeof float64    = 8 byte(s)
sizeof complex64  = 8 byte(s)
sizeof complex128 = 16 byte(s)
sizeof uintptr    = 8 byte(s)
Poznámka: na 32bitových platformách se mohou vracet různé velikosti u hodnot typů int a uintptr.

5. unsafe.Sizeof a složitější datové struktury

Samozřejmě je možné funkci unsafe.Sizeof použít i pro zjištění velikosti obsazené nějakým polem nebo řezem. Nejprve si ukažme zdrojový kód příkladu, ve kterém tyto velikosti zjišťujeme a posléze okomentujeme vypsané výsledky:

package main
 
import (
        "fmt"
        "unsafe"
)
 
func main() {
        array1 := [...]int32{1}
        array2 := [...]int32{1, 2}
        array3 := [...]int32{1, 2, 3}
 
        slice1 := []int32{1}
        slice2 := []int32{1, 2}
        slice3 := []int32{1, 2, 3}
 
        fmt.Printf("sizeof int    = %d byte(s)\n", unsafe.Sizeof(int32(42)))
        fmt.Println()
 
        fmt.Printf("sizeof array1 = %d byte(s)\n", unsafe.Sizeof(array1))
        fmt.Printf("sizeof array2 = %d byte(s)\n", unsafe.Sizeof(array2))
        fmt.Printf("sizeof array3 = %d byte(s)\n", unsafe.Sizeof(array3))
        fmt.Println()
 
        fmt.Printf("sizeof slice1 = %d byte(s)\n", unsafe.Sizeof(slice1))
        fmt.Printf("sizeof slice2 = %d byte(s)\n", unsafe.Sizeof(slice2))
        fmt.Printf("sizeof slice3 = %d byte(s)\n", unsafe.Sizeof(slice3))
}

Z vypsaných výsledků je patrné, že u polí je jejich velikost (v tomto případě) vypočtena jako počet_prvků×velikost_jednoho_prvku, což je logické. Ovšem pozor si musíme dát u řezů, protože se vždy vrátí pouze velikost struktury s informacemi o řezu. Tato struktura obsahuje ukazatel na pole (nikoli celé pole), kapacitu řezu a počet skutečně uložených prvků. Velikost řezu je tedy vždy stejná – na 64bitových systémech 3×8 bajtů:

sizeof int    = 4 byte(s)
 
sizeof array1 = 4 byte(s)
sizeof array2 = 8 byte(s)
sizeof array3 = 12 byte(s)
 
sizeof slice1 = 24 byte(s)
sizeof slice2 = 24 byte(s)
sizeof slice3 = 24 byte(s)

Dále se pokusíme zjistit, jak velkou oblast paměti je nutné rezervovat pro hodnoty, které reprezentují uživatelské datové struktury a taktéž jakou velikost mají ukazatele na hodnoty typu struktura:

package main
 
import (
        "fmt"
        "unsafe"
)
 
type Vector2D struct {
        x float32
        y float32
}
 
type Vector3D struct {
        x float32
        y float32
        z float32
}
 
type User struct {
        ID      uint32
        Name    string
        Surname string
}
 
func main() {
        vector2d := Vector2D{
                x: 10,
                y: 20,
        }
 
        vector3d := Vector3D{
                x: 10,
                y: 20,
                z: 30,
        }
 
        user := User{
                ID:      42,
                Name:    "Pepa",
                Surname: "Vyskoč",
        }
 
        fmt.Printf("sizeof str      = %d byte(s)\n", unsafe.Sizeof("foo"))
        fmt.Println()
 
        fmt.Printf("sizeof Vector2D = %d byte(s)\n", unsafe.Sizeof(vector2d))
        fmt.Printf("sizeof Vector3D = %d byte(s)\n", unsafe.Sizeof(vector3d))
        fmt.Printf("sizeof User     = %d byte(s)\n", unsafe.Sizeof(user))
        fmt.Println()
 
        fmt.Printf("sizeof &Vector2D = %d byte(s)\n", unsafe.Sizeof(&vector2d))
        fmt.Printf("sizeof &Vector3D = %d byte(s)\n", unsafe.Sizeof(&vector3d))
        fmt.Printf("sizeof &User     = %d byte(s)\n", unsafe.Sizeof(&user))
}

V testovaných příkladech velikost struktury odpovídá součtu velikostí prvků, přičemž pro typ User platí, že každý řetězec je uložen v šestnácti bajtech (nejedná se totiž o vlastní řetězec, ale opět o dvojici ukazatel+velikost, podobně jako je tomu u řezů):

sizeof str      = 16 byte(s)
 
sizeof Vector2D = 8 byte(s)
sizeof Vector3D = 12 byte(s)
sizeof User     = 40 byte(s)
 
sizeof &Vector2D = 8 byte(s)
sizeof &Vector3D = 8 byte(s)
sizeof &User     = 8 byte(s)
Poznámka: ovšem obecně to neplatí! Velikost struktury může být větší než velikosti jejich prvků, což si ostatně ukážeme v navazujících kapitolách.

6. Velikost hodnot nil různého typu

V programovacím jazyce Go se na několika místech můžeme setkat se speciální hodnotou nil. Tato hodnota se většinou používá pro reprezentaci nulové hodnoty pro různé datové typy. Proměnné v Go jsou totiž vždy inicializovány a pokud není ve zdrojových kódech explicitně uvedena jejich hodnota, je automaticky provedena inicializace na hodnotu nulovou. Z tohoto důvodu má nil význam především u těch datových typů, v nichž je nutné neinicializovanou hodnotu nějakým způsobem jednoznačně odlišit od ostatních hodnot. Příkladem mohou být ukazatele, rozhraní, mapy popř. řezy (připomeňme si, že prázdný řez je odlišný od řezu neinicializovaného).

Hodnota nil se naopak nepoužívá u číselných typů, řetězců ani u pravdivostních hodnot, protože v tomto případě je vždy proměnná inicializována – na nulu u číselných typů (celá čísla, čísla s plovoucí řádovou čárkou, komplexní čísla), na prázdný řetězec u řetězců a na hodnotu false u pravdivostních hodnot. Vše je shrnuto v následující tabulce:

Typ Výchozí hodnota
všechny numerické typy 0, 0.0, 0.0+0.0i atd.
řetězce ""
pole prvky rekurzivně inicializované dle této tabulky
ukazatel nil
řez nil
kanál nil
mapa nil

V dalším demonstračním příkladu si otestujeme, jakým způsobem dokáže funkce unsage.Sizeof zjistit velikosti hodnot nil, které jsou však různého typu:

package main
 
import (
        "fmt"
        "unsafe"
)
 
type User struct {
        ID      uint32
        Name    string
        Surname string
}
 
func main() {
        var a *int32 = nil
        var b *int64 = nil
        var c []int32 = nil
        var d interface{} = nil
        var e map[string]string = nil
        var f chan int = nil
 
        fmt.Printf("sizeof *int32      = %d byte(s)\n", unsafe.Sizeof(a))
        fmt.Printf("sizeof *int64      = %d byte(s)\n", unsafe.Sizeof(b))
        fmt.Printf("sizeof slice       = %d byte(s)\n", unsafe.Sizeof(c))
        fmt.Printf("sizeof {}interface = %d byte(s)\n", unsafe.Sizeof(d))
        fmt.Printf("sizeof map         = %d byte(s)\n", unsafe.Sizeof(e))
        fmt.Printf("sizeof chan        = %d byte(s)\n", unsafe.Sizeof(f))
}

Z výsledků je patrné, že i když ve zdrojových kódech (nebo při tisku na terminál) vidíme hodnotu nil, může se interně jednat o bloky, které zabírají různě velké místo v operační paměti:

sizeof *int32      = 8 byte(s)
sizeof *int64      = 8 byte(s)
sizeof slice       = 24 byte(s)
sizeof {}interface = 16 byte(s)
sizeof map         = 8 byte(s)
sizeof chan        = 8 byte(s)

7. Vliv pořadí prvků ve struktuře na její celkové velikosti

Interně jsou prvky ve struktuře zarovnány způsobem, který zjistíme (o několik kapitol níže) funkcí unsafe.Alignof. Mezi prvky struktur tedy mohou být vkládány výplně (padding), jejichž existence, umístění i velikost závisí na typu a taktéž na pořadí prvků struktury. Můžeme si to ukázat na čtveřici struktur, které obsahují prvky stejného typu, ovšem v odlišném pořadí. Už jen prohození pořadí prvků může vést k tomu, že se změní celková velikost struktury:

package main
 
import (
        "fmt"
        "unsafe"
)
 
type Struct1 struct {
        x byte
        y uint16
        z uint32
}
 
type Struct2 struct {
        z uint32
        y uint16
        x byte
}
 
type Struct3 struct {
        x byte
        z uint32
        y uint16
}
 
type Struct4 struct {
        y uint16
        z uint32
        x byte
}
 
func main() {
        fmt.Printf("sizeof Struct1   = %d byte(s)\n", unsafe.Sizeof(Struct1{}))
        fmt.Printf("sizeof Struct2   = %d byte(s)\n", unsafe.Sizeof(Struct2{}))
        fmt.Printf("sizeof Struct3   = %d byte(s)\n", unsafe.Sizeof(Struct3{}))
        fmt.Printf("sizeof Struct4   = %d byte(s)\n", unsafe.Sizeof(Struct4{}))
}

Z výsledků je patrné, že v některých případech kvůli zarovnání naroste velikost struktury o čtyři bajty:

sizeof Struct1   = 8 byte(s)
sizeof Struct2   = 8 byte(s)
sizeof Struct3   = 12 byte(s)
sizeof Struct4   = 12 byte(s)
Poznámka: samotný nárůst je v tomto případě sice minimální, ovšem pokud by se jednalo o pole či řez s milionem struktur, už by se jednalo o významný údaj. Zde nám tedy může knihovna unsafe pomoci.

8. Vyhodnocení unsafe.Sizeof na konstantu

Připomeňme si, že v jazyku Go je možné pracovat s konstantami. Jejich hodnoty jsou vyhodnoceny v čase překladu. Pokud tomu tak není (tj. není možné vypočítat hodnotu konstanty), bude překladač hlásit chybu:

package main
 
import (
        "fmt"
        "math"
)
 
func main() {
        const x = math.Pi / 2
        const y = math.Sin(x)
        fmt.Println("vim-go")
}

Tento program není možné přeložit, protože hodnotu konstanty x překladač nezjistí v době překladu (v tomto případě by ji zjistit mohl, ovšem spuštěním kódu z balíčku math):

$ go build not_constant.go
 
# command-line-arguments
./not_constant.go:10:12: math.Sin(x) (value of type float64) is not constant

Na druhou stranu výsledky volání funkce unsafe.Sizeof ve skutečnosti konstantami jsou, což je sice poněkud zvláštní, ale velmi užitečné. Z tohoto důvodu je možné přeložit a spustit i následující program:

package main
 
import (
        "fmt"
        "unsafe"
)
 
type Vector2D struct {
        x float32
        y float32
}
 
type Vector3D struct {
        x float32
        y float32
        z float32
}
 
type User struct {
        ID      uint32
        Name    string
        Surname string
}
 
type Struct1 struct {
        x byte
        y uint16
        z uint32
}
 
type Struct2 struct {
        z uint32
        y uint16
        x byte
}
 
type Struct3 struct {
        x byte
        z uint32
        y uint16
}
 
type Struct4 struct {
        y uint16
        z uint32
        x byte
}
 
func main() {
        const s01 = unsafe.Sizeof(int8(0))
        const s02 = unsafe.Sizeof(int16(0))
        const s03 = unsafe.Sizeof(int32(0))
        const s04 = unsafe.Sizeof(int64(0))
        const s05 = unsafe.Sizeof(int(0))
        const s06 = unsafe.Sizeof(float32(0))
        const s07 = unsafe.Sizeof(float64(0))
        const s08 = unsafe.Sizeof(complex64(0))
        const s09 = unsafe.Sizeof(complex128(0))
        const s10 = unsafe.Sizeof(uintptr(0))
 
        fmt.Printf("sizeof int8        = %d byte(s)\n", s01)
        fmt.Printf("sizeof int16       = %d byte(s)\n", s02)
        fmt.Printf("sizeof int32       = %d byte(s)\n", s03)
        fmt.Printf("sizeof int64       = %d byte(s)\n", s04)
        fmt.Printf("sizeof int         = %d byte(s)\n", s05)
        fmt.Printf("sizeof float32     = %d byte(s)\n", s06)
        fmt.Printf("sizeof float64     = %d byte(s)\n", s07)
        fmt.Printf("sizeof complex64   = %d byte(s)\n", s08)
        fmt.Printf("sizeof complex128  = %d byte(s)\n", s09)
        fmt.Printf("sizeof uintptr     = %d byte(s)\n", s10)
        fmt.Println()
 
        array1 := [...]int32{1}
        array2 := [...]int32{1, 2}
        array3 := [...]int32{1, 2, 3}
 
        const s11 = unsafe.Sizeof(array1)
        const s12 = unsafe.Sizeof(array2)
        const s13 = unsafe.Sizeof(array3)
 
        fmt.Printf("sizeof array1      = %d byte(s)\n", s11)
        fmt.Printf("sizeof array2      = %d byte(s)\n", s12)
        fmt.Printf("sizeof array3      = %d byte(s)\n", s13)
        fmt.Println()
 
        slice1 := []int32{1}
        slice2 := []int32{1, 2}
        slice3 := []int32{1, 2, 3}
 
        const s14 = unsafe.Sizeof(slice1)
        const s15 = unsafe.Sizeof(slice2)
        const s16 = unsafe.Sizeof(slice3)
 
        fmt.Printf("sizeof slice1      = %d byte(s)\n", s14)
        fmt.Printf("sizeof slice2      = %d byte(s)\n", s15)
        fmt.Printf("sizeof slice3      = %d byte(s)\n", s16)
        fmt.Println()
 
        fmt.Printf("sizeof str         = %d byte(s)\n", unsafe.Sizeof("foo"))
        fmt.Println()
 
        vector2d := Vector2D{
                x: 10,
                y: 20,
        }
 
        vector3d := Vector3D{
                x: 10,
                y: 20,
                z: 30,
        }
 
        user := User{
                ID:      42,
                Name:    "Pepa",
                Surname: "Vyskoč",
        }
 
        const s17 = unsafe.Sizeof(vector2d)
        const s18 = unsafe.Sizeof(vector3d)
        const s19 = unsafe.Sizeof(user)
        const s20 = unsafe.Sizeof(&vector2d)
        const s21 = unsafe.Sizeof(&vector3d)
        const s22 = unsafe.Sizeof(&user)
 
        fmt.Printf("sizeof Vector2D    = %d byte(s)\n", s17)
        fmt.Printf("sizeof Vector3D    = %d byte(s)\n", s18)
        fmt.Printf("sizeof User        = %d byte(s)\n", s19)
        fmt.Println()
 
        fmt.Printf("sizeof &Vector2D   = %d byte(s)\n", s20)
        fmt.Printf("sizeof &Vector3D   = %d byte(s)\n", s21)
        fmt.Printf("sizeof &User       = %d byte(s)\n", s22)
 
        var a *int32 = nil
        var b *int64 = nil
        var c []int32 = nil
        var d interface{} = nil
        var e map[string]string = nil
        var f chan int = nil
 
        const s23 = unsafe.Sizeof(a)
        const s24 = unsafe.Sizeof(b)
        const s25 = unsafe.Sizeof(c)
        const s26 = unsafe.Sizeof(d)
        const s27 = unsafe.Sizeof(e)
        const s28 = unsafe.Sizeof(f)
 
        const s29 = unsafe.Sizeof(Struct1{})
        const s30 = unsafe.Sizeof(Struct2{})
        const s31 = unsafe.Sizeof(Struct3{})
        const s32 = unsafe.Sizeof(Struct4{})
 
        fmt.Printf("sizeof *int32      = %d byte(s)\n", s23)
        fmt.Printf("sizeof *int64      = %d byte(s)\n", s24)
        fmt.Printf("sizeof slice       = %d byte(s)\n", s25)
        fmt.Printf("sizeof {}interface = %d byte(s)\n", s26)
        fmt.Printf("sizeof map         = %d byte(s)\n", s27)
        fmt.Printf("sizeof chan        = %d byte(s)\n", s28)
 
        fmt.Printf("sizeof Struct1     = %d byte(s)\n", s29)
        fmt.Printf("sizeof Struct2     = %d byte(s)\n", s30)
        fmt.Printf("sizeof Struct3     = %d byte(s)\n", s31)
        fmt.Printf("sizeof Struct4     = %d byte(s)\n", s32)
}

Výsledky budou vypadat následovně:

sizeof int8        = 1 byte(s)
sizeof int16       = 2 byte(s)
sizeof int32       = 4 byte(s)
sizeof int64       = 8 byte(s)
sizeof int         = 8 byte(s)
sizeof float32     = 4 byte(s)
sizeof float64     = 8 byte(s)
sizeof complex64   = 8 byte(s)
sizeof complex128  = 16 byte(s)
sizeof uintptr     = 8 byte(s)
 
sizeof array1      = 4 byte(s)
sizeof array2      = 8 byte(s)
sizeof array3      = 12 byte(s)
 
sizeof slice1      = 24 byte(s)
sizeof slice2      = 24 byte(s)
sizeof slice3      = 24 byte(s)
 
sizeof str         = 16 byte(s)
 
sizeof Vector2D    = 8 byte(s)
sizeof Vector3D    = 12 byte(s)
sizeof User        = 40 byte(s)
 
sizeof &Vector2D   = 8 byte(s)
sizeof &Vector3D   = 8 byte(s)
sizeof &User       = 8 byte(s)
sizeof *int32      = 8 byte(s)
sizeof *int64      = 8 byte(s)
sizeof slice       = 24 byte(s)
sizeof {}interface = 16 byte(s)
sizeof map         = 8 byte(s)
sizeof chan        = 8 byte(s)
sizeof Struct1     = 8 byte(s)
sizeof Struct2     = 8 byte(s)
sizeof Struct3     = 12 byte(s)
sizeof Struct4     = 12 byte(s)

9. Funkce unsafe.Alignof

Druhou funkcí z balíčku unsafe, se kterou se dnes seznámíme, je funkce nazvaná unsafe.Alignof. I pro tuto funkci je pochopitelně možné získat nápovědu:

$ go doc unsafe.Alignof

Nápověda je zobrazena na terminálu:

package unsafe // import "unsafe"
 
func Alignof(x ArbitraryType) uintptr
    Alignof takes an expression x of any type and returns the required
    alignment of a hypothetical variable v as if v was declared via var v = x.
    It is the largest value m such that the address of v is always zero mod m.
    It is the same as the value returned by reflect.TypeOf(x).Align().
    As a special case, if a variable s is of struct type and f is a field
    within that struct, then Alignof(s.f) will return the required alignment
    of a field of that type within a struct. This case is the same as the value
    returned by reflect.TypeOf(s.f).FieldAlign(). The return value of Alignof
    is a Go constant if the type of the argument does not have variable size.
    (See the description of Sizeof for a definition of variable sized types.)

Tato funkce se do určité míry podobá unsafe.Sizef, protože i jí je možné předat výraz libovolného typu a i navrácená hodnota je konstantní (z pohledu překladače). unsafe.Alignof vrací požadované zarovnání hodnoty v operační paměti. Pokud se například vrátí hodnota 2, znamená to, že paměťový blok s hodnotou bude zarovnán na sudé adresy. V případě, že se vrátí hodnota 8, bude zarovnání provedeno na adresy dělitelné osmi atd. Ve všech případech se předpokládá, že adresování probíhá po bajtech.

10. Zarovnání u základních datových typů i u struktur

Otestujme si nyní, jak musí být zarovnány hodnoty základních datových typů v paměti. Je to snadné:

package main
 
import (
        "fmt"
        "unsafe"
)
 
func main() {
        fmt.Printf("alignof int8       = %d byte(s)\n", unsafe.Alignof(int8(0)))
        fmt.Printf("alignof int16      = %d byte(s)\n", unsafe.Alignof(int16(0)))
        fmt.Printf("alignof int32      = %d byte(s)\n", unsafe.Alignof(int32(0)))
        fmt.Printf("alignof int64      = %d byte(s)\n", unsafe.Alignof(int64(0)))
        fmt.Printf("alignof int        = %d byte(s)\n", unsafe.Alignof(int(0)))
        fmt.Printf("alignof float32    = %d byte(s)\n", unsafe.Alignof(float32(0)))
        fmt.Printf("alignof float64    = %d byte(s)\n", unsafe.Alignof(float64(0)))
        fmt.Printf("alignof complex64  = %d byte(s)\n", unsafe.Alignof(complex64(0)))
        fmt.Printf("alignof complex128 = %d byte(s)\n", unsafe.Alignof(complex128(0)))
        fmt.Printf("alignof uintptr    = %d byte(s)\n", unsafe.Alignof(uintptr(0)))
}

Výsledky pro 64bitovou platformu vypadají takto:

alignof int8       = 1 byte(s)
alignof int16      = 2 byte(s)
alignof int32      = 4 byte(s)
alignof int64      = 8 byte(s)
alignof int        = 8 byte(s)
alignof float32    = 4 byte(s)
alignof float64    = 8 byte(s)
alignof complex64  = 4 byte(s)
alignof complex128 = 8 byte(s)
alignof uintptr    = 8 byte(s)

Podobným způsobem je možné zjistit i zarovnání celých struktur v paměti:

package main
 
import (
        "fmt"
        "unsafe"
)
 
type Vector2D struct {
        x float32
        y float32
}
 
type Vector3D struct {
        x float32
        y float32
        z float32
}
 
type User struct {
        ID      uint32
        Name    string
        Surname string
}
 
func main() {
        vector2d := Vector2D{
                x: 10,
                y: 20,
        }
 
        vector3d := Vector3D{
                x: 10,
                y: 20,
                z: 30,
        }
 
        user := User{
                ID:      42,
                Name:    "Pepa",
                Surname: "Vyskoč",
        }
 
        fmt.Printf("alignof Vector2D = %d byte(s)\n", unsafe.Alignof(vector2d))
        fmt.Printf("alignof Vector3D = %d byte(s)\n", unsafe.Alignof(vector3d))
        fmt.Printf("alignof User     = %d byte(s)\n", unsafe.Alignof(user))
        fmt.Println()
}

Výsledky mohou být v tomto případě poněkud překvapivé, protože ukazují, že se zarovnání může odlišovat, i když by se mohlo předpokládat, že bude vždy stejné (32 nebo 64 bitů, v závislosti na platformě). Ovšem ve skutečnosti je zarovnání celé struktury omezeno zarovnáním jejích prvků:

alignof Vector2D = 4 byte(s)
alignof Vector3D = 4 byte(s)
alignof User     = 8 byte(s)
Poznámka: struktury jsou v jazyku Go hodnotovými typy, nikoli referencemi (což je případ objektů v Javě a struktur v Pythonu).

11. Vliv typů prvků struktury na její celkové zarovnání

Celkové zarovnání struktury do značné míry závisí na tom, jaké typy prvků struktura obsahuje a v jakém pořadí jsou ve struktuře uloženy. Opět si to otestujme na jednoduchém demonstračním příkladu se strukturami obsahujícími prvky typu byte:

package main
 
import (
        "fmt"
        "unsafe"
)
 
type Struct1 struct {
        x byte
}
 
type Struct2 struct {
        x byte
        y byte
}
 
type Struct3 struct {
        x byte
        y byte
        z byte
}
 
type Struct4 struct {
        x byte
        y byte
        z byte
        w byte
}
 
func main() {
        fmt.Printf("alignof Struct1   = %d byte(s)\n", unsafe.Alignof(Struct1{}))
        fmt.Printf("alignof Struct2   = %d byte(s)\n", unsafe.Alignof(Struct2{}))
        fmt.Printf("alignof Struct3   = %d byte(s)\n", unsafe.Alignof(Struct3{}))
        fmt.Printf("alignof Struct4   = %d byte(s)\n", unsafe.Alignof(Struct4{}))
}

Pro struktury s prvky typu byte se (nezávisle na počtu prvků) vždy vrátí zarovnání 1, tedy vlastně žádné speciální zarovnání:

alignof Struct1   = 1 byte(s)
alignof Struct2   = 1 byte(s)
alignof Struct3   = 1 byte(s)
alignof Struct4   = 1 byte(s)

Jinak je tomu ovšem tehdy, pokud struktury obsahují prvky typu int32, float32 či například float64:

package main
 
import (
        "fmt"
        "unsafe"
)
 
type Struct1 struct {
        i int32
        x byte
}
 
type Struct2 struct {
        i int32
        x byte
        y byte
}
 
type Struct3 struct {
        i int32
        x byte
        y byte
        z byte
}
 
type Struct4 struct {
        i int32
        x byte
        y byte
        z byte
        w byte
}
 
func main() {
        fmt.Printf("alignof Struct1   = %d byte(s)\n", unsafe.Alignof(Struct1{}))
        fmt.Printf("alignof Struct2   = %d byte(s)\n", unsafe.Alignof(Struct2{}))
        fmt.Printf("alignof Struct3   = %d byte(s)\n", unsafe.Alignof(Struct3{}))
        fmt.Printf("alignof Struct4   = %d byte(s)\n", unsafe.Alignof(Struct4{}))
}

Nyní je vynuceno zarovnání na adresu dělitelnou čtyřmi:

alignof Struct1   = 4 byte(s)
alignof Struct2   = 4 byte(s)
alignof Struct3   = 4 byte(s)
alignof Struct4   = 4 byte(s)
Poznámka: dokážete sami odhadnout, jaké bude zarovnání celé struktury, pokud bude struktura začínat prvkem typu byte a teprve dalším prvkem typu int32?

12. Vyhodnocení unsafe.Alignof na konstantu

I funkce unsafe.Alignof je zvláštní tím, že se vyhodnocuje v čase překladu, takže její výsledek je možné uložit do konstanty. To je ukázáno v dalším demonstračním příkladu, ve kterém výsledky všech volání této funkce uložíme do konstant a teprve hodnoty těchto konstant jsou vypsány:

package main
 
import (
        "fmt"
        "unsafe"
)
 
type Vector2D struct {
        x float32
        y float32
}
 
type Vector3D struct {
        x float32
        y float32
        z float32
}
 
type User struct {
        ID      uint32
        Name    string
        Surname string
}
 
type Struct1 struct {
        x byte
}
 
type Struct2 struct {
        x byte
        y byte
}
 
type Struct3 struct {
        x byte
        y byte
        z byte
}
 
type Struct4 struct {
        x byte
        y byte
        z byte
        w byte
}
 
type Struct5 struct {
        i int32
        x byte
}
 
type Struct6 struct {
        i int32
        x byte
        y byte
}
 
type Struct7 struct {
        i int32
        x byte
        y byte
        z byte
}
 
type Struct8 struct {
        i int32
        x byte
        y byte
        z byte
        w byte
}
 
func main() {
        const c01 = unsafe.Alignof(int8(0))
        const c02 = unsafe.Alignof(int16(0))
        const c03 = unsafe.Alignof(int32(0))
        const c04 = unsafe.Alignof(int64(0))
        const c05 = unsafe.Alignof(int(0))
        const c06 = unsafe.Alignof(float32(0))
        const c07 = unsafe.Alignof(float64(0))
        const c08 = unsafe.Alignof(complex64(0))
        const c09 = unsafe.Alignof(complex128(0))
        const c10 = unsafe.Alignof(uintptr(0))
 
        fmt.Printf("alignof int8       = %d byte(s)\n", c01)
        fmt.Printf("alignof int16      = %d byte(s)\n", c02)
        fmt.Printf("alignof int32      = %d byte(s)\n", c03)
        fmt.Printf("alignof int64      = %d byte(s)\n", c04)
        fmt.Printf("alignof int        = %d byte(s)\n", c05)
        fmt.Printf("alignof float32    = %d byte(s)\n", c06)
        fmt.Printf("alignof float64    = %d byte(s)\n", c07)
        fmt.Printf("alignof complex64  = %d byte(s)\n", c08)
        fmt.Printf("alignof complex128 = %d byte(s)\n", c09)
        fmt.Printf("alignof uintptr    = %d byte(s)\n", c10)
        fmt.Println()
 
        vector2d := Vector2D{
                x: 10,
                y: 20,
        }
 
        vector3d := Vector3D{
                x: 10,
                y: 20,
                z: 30,
        }
 
        user := User{
                ID:      42,
                Name:    "Pepa",
                Surname: "Vyskoč",
        }
 
        const c11 = unsafe.Alignof(vector2d)
        const c12 = unsafe.Alignof(vector3d)
        const c13 = unsafe.Alignof(user)
 
        fmt.Printf("alignof Vector2D   = %d byte(s)\n", c11)
        fmt.Printf("alignof Vector3D   = %d byte(s)\n", c12)
        fmt.Printf("alignof User       = %d byte(s)\n", c13)
        fmt.Println()
 
        const c14 = unsafe.Alignof(Struct1{})
        const c15 = unsafe.Alignof(Struct2{})
        const c16 = unsafe.Alignof(Struct3{})
        const c17 = unsafe.Alignof(Struct4{})
 
        fmt.Printf("alignof Struct1    = %d byte(s)\n", c14)
        fmt.Printf("alignof Struct2    = %d byte(s)\n", c15)
        fmt.Printf("alignof Struct3    = %d byte(s)\n", c16)
        fmt.Printf("alignof Struct4    = %d byte(s)\n", c17)
        fmt.Println()
 
        const c18 = unsafe.Alignof(Struct5{})
        const c19 = unsafe.Alignof(Struct6{})
        const c20 = unsafe.Alignof(Struct7{})
        const c21 = unsafe.Alignof(Struct8{})
 
        fmt.Printf("alignof Struct5    = %d byte(s)\n", c18)
        fmt.Printf("alignof Struct6    = %d byte(s)\n", c19)
        fmt.Printf("alignof Struct7    = %d byte(s)\n", c20)
        fmt.Printf("alignof Struct8    = %d byte(s)\n", c21)
}

Tento zdrojový kód bude stále přeložitelný a po svém spuštění by měl vypsat následující zprávy:

alignof int8       = 1 byte(s)
alignof int16      = 2 byte(s)
alignof int32      = 4 byte(s)
alignof int64      = 8 byte(s)
alignof int        = 8 byte(s)
alignof float32    = 4 byte(s)
alignof float64    = 8 byte(s)
alignof complex64  = 4 byte(s)
alignof complex128 = 8 byte(s)
alignof uintptr    = 8 byte(s)
 
alignof Vector2D   = 4 byte(s)
alignof Vector3D   = 4 byte(s)
alignof User       = 8 byte(s)
 
alignof Struct1    = 1 byte(s)
alignof Struct2    = 1 byte(s)
alignof Struct3    = 1 byte(s)
alignof Struct4    = 1 byte(s)
 
alignof Struct5    = 4 byte(s)
alignof Struct6    = 4 byte(s)
alignof Struct7    = 4 byte(s)
alignof Struct8    = 4 byte(s)

13. Funkce unsafe.Offsetof

Třetí funkcí z balíčku unsafe, s níž se dnes setkáme, je funkce nazvaná unsafe.Offsetof. I pro ni je pochopitelně připravena nápověda:

$ go doc unsafe.Offsetof

Nápověda zobrazená na terminálu:

package unsafe // import "unsafe"
 
func Offsetof(x ArbitraryType) uintptr
    Offsetof returns the offset within the struct of the field represented by x,
    which must be of the form structValue.field. In other words, it returns the
    number of bytes between the start of the struct and the start of the field.
    The return value of Offsetof is a Go constant if the type of the argument x
    does not have variable size. (See the description of Sizeof for a definition
    of variable sized types.)

Tato funkce slouží k výpočtu (relativního) offsetu zvoleného prvku libovolné struktury. Výsledkem je opět konstantní hodnota. Tuto funkci je možné s výhodou použít pro zjištění, ve kterých místech struktury jsou použity výplňové bajty (padding), které zvětšují celkovou velikost struktury a vedou i ke zpomalení běhu aplikace (kvůli vyšší pravděpodobnosti výpadku cachí).

14. Přečtení offsetů jednotlivých prvků datových struktur

Funkci unsafe.Offsetof otestujeme na datové struktuře Vector3D, která obsahuje trojici prvků typu float32. Zjistíme offsety všech tří prvků, které jsou následně vypsány na terminál:

package main
 
import (
        "fmt"
        "unsafe"
)
 
type Vector3D struct {
        x float32
        y float32
        z float32
}
 
func main() {
        vector3d := Vector3D{
                x: 10,
                y: 20,
                z: 30,
        }
 
        fmt.Printf("offsetof Vector3D.x = %d byte(s)\n", unsafe.Offsetof(vector3d.x))
        fmt.Printf("offsetof Vector3D.y = %d byte(s)\n", unsafe.Offsetof(vector3d.y))
        fmt.Printf("offsetof Vector3D.a = %d byte(s)\n", unsafe.Offsetof(vector3d.z))
        fmt.Println()
}

Zobrazené výsledky by v tomto případě neměly být překvapivé, protože již víme, že se pro hodnoty typu float32 nepoužívají výplně a zarovnání musí být nastaveno na adresy dělitelné čtyřmi (což splňuje i celá struktura):

offsetof Vector3D.x = 0 byte(s)
offsetof Vector3D.y = 4 byte(s)
offsetof Vector3D.a = 8 byte(s)

15. Offsety prvků ve strukturách obsahujících výplňové bajty

V případě, že datové struktury obsahují výplňové bajty (padding), bude jejich velikost vrácená funkcí unsafe.Sizeof větší, než pouhý součet velikostí jednotlivých prvků takové struktury. A současně se pozici výplňových bajtů (nepřímo) dozvíme právě z výsledků získaných funkcí unsafe.Offsetof. Můžeme si to ostatně velmi snadno otestovat na strukturách, které se liší pořadím prvků s různými bitovými šířkami:

package main
 
import (
        "fmt"
        "unsafe"
)
 
type Struct1 struct {
        x byte
        y byte
        z byte
}
 
type Struct2 struct {
        x byte
        y byte
        z uint32
}
 
type Struct3 struct {
        x byte
        y uint32
        z byte
}
 
type Struct4 struct {
        x uint32
        y byte
        z byte
}
 
type Struct5 struct {
        x byte
        y uint16
        z uint32
}
 
type Struct6 struct {
        z uint32
        y uint16
        x byte
}
 
type Struct7 struct {
        x byte
        z uint32
        y uint16
}
 
type Struct8 struct {
        y uint16
        z uint32
        x byte
}
 
func main() {
        fmt.Printf("offsetof Struct1.x   = %d byte(s)\n", unsafe.Offsetof(Struct1{}.x))
        fmt.Printf("offsetof Struct1.y   = %d byte(s)\n", unsafe.Offsetof(Struct1{}.y))
        fmt.Printf("offsetof Struct1.z   = %d byte(s)\n", unsafe.Offsetof(Struct1{}.z))
        fmt.Println()
        fmt.Printf("offsetof Struct2.x   = %d byte(s)\n", unsafe.Offsetof(Struct2{}.x))
        fmt.Printf("offsetof Struct2.y   = %d byte(s)\n", unsafe.Offsetof(Struct2{}.y))
        fmt.Printf("offsetof Struct2.z   = %d byte(s)\n", unsafe.Offsetof(Struct2{}.z))
        fmt.Println()
        fmt.Printf("offsetof Struct3.x   = %d byte(s)\n", unsafe.Offsetof(Struct3{}.x))
        fmt.Printf("offsetof Struct3.y   = %d byte(s)\n", unsafe.Offsetof(Struct3{}.y))
        fmt.Printf("offsetof Struct3.z   = %d byte(s)\n", unsafe.Offsetof(Struct3{}.z))
        fmt.Println()
        fmt.Printf("offsetof Struct4.x   = %d byte(s)\n", unsafe.Offsetof(Struct4{}.x))
        fmt.Printf("offsetof Struct4.y   = %d byte(s)\n", unsafe.Offsetof(Struct4{}.y))
        fmt.Printf("offsetof Struct4.z   = %d byte(s)\n", unsafe.Offsetof(Struct4{}.z))
        fmt.Println()
        fmt.Printf("offsetof Struct5.x   = %d byte(s)\n", unsafe.Offsetof(Struct5{}.x))
        fmt.Printf("offsetof Struct5.y   = %d byte(s)\n", unsafe.Offsetof(Struct5{}.y))
        fmt.Printf("offsetof Struct5.z   = %d byte(s)\n", unsafe.Offsetof(Struct5{}.z))
        fmt.Println()
        fmt.Printf("offsetof Struct6.x   = %d byte(s)\n", unsafe.Offsetof(Struct6{}.x))
        fmt.Printf("offsetof Struct6.y   = %d byte(s)\n", unsafe.Offsetof(Struct6{}.y))
        fmt.Printf("offsetof Struct6.z   = %d byte(s)\n", unsafe.Offsetof(Struct6{}.z))
        fmt.Println()
        fmt.Printf("offsetof Struct7.x   = %d byte(s)\n", unsafe.Offsetof(Struct7{}.x))
        fmt.Printf("offsetof Struct7.y   = %d byte(s)\n", unsafe.Offsetof(Struct7{}.y))
        fmt.Printf("offsetof Struct7.z   = %d byte(s)\n", unsafe.Offsetof(Struct7{}.z))
        fmt.Println()
        fmt.Printf("offsetof Struct8.x   = %d byte(s)\n", unsafe.Offsetof(Struct8{}.x))
        fmt.Printf("offsetof Struct8.y   = %d byte(s)\n", unsafe.Offsetof(Struct8{}.y))
        fmt.Printf("offsetof Struct8.z   = %d byte(s)\n", unsafe.Offsetof(Struct8{}.z))
        fmt.Println()
}

Povšimněte si, že v některých případech výplňové bajty použity nejsou (Struct1), ovšem v případě, že na sebe například navazují prvky byte+uint32 se již výplňové bajty používají (Struct2 a mnohé další struktury z tohoto příkladu):

offsetof Struct1.x   = 0 byte(s)
offsetof Struct1.y   = 1 byte(s)
offsetof Struct1.z   = 2 byte(s)
 
offsetof Struct2.x   = 0 byte(s)
offsetof Struct2.y   = 1 byte(s)
offsetof Struct2.z   = 4 byte(s)
 
offsetof Struct3.x   = 0 byte(s)
offsetof Struct3.y   = 4 byte(s)
offsetof Struct3.z   = 8 byte(s)
 
offsetof Struct4.x   = 0 byte(s)
offsetof Struct4.y   = 4 byte(s)
offsetof Struct4.z   = 5 byte(s)
 
offsetof Struct5.x   = 0 byte(s)
offsetof Struct5.y   = 2 byte(s)
offsetof Struct5.z   = 4 byte(s)
 
offsetof Struct6.x   = 6 byte(s)
offsetof Struct6.y   = 4 byte(s)
offsetof Struct6.z   = 0 byte(s)
 
offsetof Struct7.x   = 0 byte(s)
offsetof Struct7.y   = 8 byte(s)
offsetof Struct7.z   = 4 byte(s)
 
offsetof Struct8.x   = 8 byte(s)
offsetof Struct8.y   = 0 byte(s)
offsetof Struct8.z   = 4 byte(s)

16. Datový typ unsafe.Pointer

Ve standardní knihovně unsafe nalezneme i definice tří datových typů. Jedním z nejdůležitějších (a nyní skutečně „nebezpečných“) typů je unsafe.Pointer. Tento typ se používá především ve chvíli, kdy je nutné volat nativní kód, který je vytvořen například v programovacím jazyku C atd.:

$ go doc unsafe.Pointer

Nápověda k tomuto datovému typu:

package unsafe // import "unsafe"
 
type Pointer *ArbitraryType
    Pointer represents a pointer to an arbitrary type. There are four special
    operations available for type Pointer that are not available for other
    types:
      - A pointer value of any type can be converted to a Pointer.
      - A Pointer can be converted to a pointer value of any type.
      - A uintptr can be converted to a Pointer.
      - A Pointer can be converted to a uintptr.

17. Explicitní (ruční) alokace a dealokace paměti

Jak jsme si řekli v předchozí kapitole, používá se datový typ unsafe.Pointer například při volání nativního céčkového kódu atd. To je ukázáno v dalším demonstračním příkladu, ve kterém je nejdříve v operační paměti alokován céčkovský řetězec (což je odlišná struktura, než řetězec v jazyce Go!) a následně je zavolána céčkovská funkce puts, která tento řetězec vypíše na terminál. Povšimněte si, že céčkovské funkce se volají přes pseudobalíček nazvaný C:

package main
 
// #include <stdio.h>
// #include <stdlib.h>
import "C"
import "unsafe"
 
func main() {
        cs := C.CString("Hello world!")
        C.puts(cs)
}

Výše uvedený demonstrační příklad ovšem není korektní, protože céčkovský řetězec nebude uvolněn správcem paměti jazyka Go. O uvolnění se musíme explicitně postarat sami zavoláním céčkovské funkce free – a právě zde se objevuje první použití typu unsafe.Pointer použitý pro přetypování:

package main
 
// #include <stdio.h>
// #include <stdlib.h>
import "C"
import "unsafe"
 
func main() {
        cs := C.CString("Hello world!")
        C.puts(cs)
        C.free(unsafe.Pointer(cs))
}

Ostatně sami se můžeme snadno přesvědčit, že nebude docházet k OOM, a to ani v případě, že se bude alokace+dealokace provádět opakovaně:

package main
 
// #include <stdio.h>
// #include <stdlib.h>
import "C"
import "unsafe"
 
func main() {
        for {
                cs := C.CString("Hello world!")
                C.puts(cs)
                C.free(unsafe.Pointer(cs))
        }
}

18. Unsafe ukazatele jsou skutečně unsafe

V programovacím jazyce Go se při práci s běžnými ukazateli hlídá oblast platnosti hodnot, jejichž adresa je uložena v ukazateli. To například znamená, že správce paměti hodnotu neuvolní z paměti ve chvíli, kdy stále existuje ukazatel s její adresou atd. Ovšem u unsafe ukazatelů tomu tak není, takže může docházet k několika problémům, které je nutné řešit podobně, jako v běžném céčku – tedy ručně a bez pomoci překladače.

Příkladem může být uvolnění bloku paměti s následným přístupem do tohoto bloku přes unsafe ukazatel:

package main
 
// #include <stdio.h>
// #include <stdlib.h>
import "C"
import "unsafe"
 
func main() {
        cs := C.CString("Hello world!")
        C.free(unsafe.Pointer(cs))
        C.puts(cs)
}
Poznámka: zde bychom již neměli volat C.puts, protože nám paměť s řetězcem již nepatří. Chování tohoto programu je nedefinované a obecně chybné.

Již uvolněný blok paměti není možné znovu uvolnit, na což nás může (ale nemusí) upozornit runtime knihovna jazyka C:

Školení Hacking

package main
 
// #include <stdio.h>
// #include <stdlib.h>
import "C"
import "unsafe"
 
func main() {
        cs := C.CString("Hello world!")
        C.puts(cs)
        C.free(unsafe.Pointer(cs))
        C.free(unsafe.Pointer(cs))
}

Opět se jedná o nedefinované a obecně chybné chování. Na mém systému dojde k detekci problému a pádu aplikace (SIGABRT):

free(): double free detected in tcache 2
SIGABRT: abort
PC=0x7f239b7d2034 m=0 sigcode=18446744073709551610
signal arrived during cgo execution
 
goroutine 1 gp=0xc0000061c0 m=0 mp=0x4fbe40 [syscall]:
runtime.cgocall(0x46ada0, 0xc000080ef8)
    /usr/lib/golang/src/runtime/cgocall.go:167 +0x4b fp=0xc000080ed0 sp=0xc000080e98 pc=0x45d4cb
main._Cfunc_free(0x38996050)
    _cgo_gotypes.go:77 +0x3f fp=0xc000080ef8 sp=0xc000080ed0 pc=0x46aaff
main.main.func2(0x38996050)
    /home/ptisnovs/xy/unsafe/unsafe_pointer_5.go:12 +0x34 fp=0xc000080f28 sp=0xc000080ef8 pc=0x46acd4
main.main()
    /home/ptisnovs/xy/unsafe/unsafe_pointer_5.go:12 +0x4f fp=0xc000080f50 sp=0xc000080f28 pc=0x46ac8f
runtime.main()
    /usr/lib/golang/src/runtime/proc.go:272 +0x28b fp=0xc000080fe0 sp=0xc000080f50 pc=0x43370b
runtime.goexit({})
    /usr/lib/golang/src/runtime/asm_amd64.s:1700 +0x1 fp=0xc000080fe8 sp=0xc000080fe0 pc=0x4664e1
 
goroutine 2 gp=0xc000006c40 m=nil [force gc (idle)]:
runtime.gopark(0x0?, 0x0?, 0x0?, 0x0?, 0x0?)
    /usr/lib/golang/src/runtime/proc.go:424 +0xce fp=0xc000068fa8 sp=0xc000068f88 pc=0x46016e
runtime.goparkunlock(...)
    /usr/lib/golang/src/runtime/proc.go:430
runtime.forcegchelper()
    /usr/lib/golang/src/runtime/proc.go:337 +0xb3 fp=0xc000068fe0 sp=0xc000068fa8 pc=0x433a53
runtime.goexit({})
    /usr/lib/golang/src/runtime/asm_amd64.s:1700 +0x1 fp=0xc000068fe8 sp=0xc000068fe0 pc=0x4664e1
created by runtime.init.7 in goroutine 1
    /usr/lib/golang/src/runtime/proc.go:325 +0x1a
 
goroutine 3 gp=0xc000007180 m=nil [GC sweep wait]:
runtime.gopark(0x0?, 0x0?, 0x0?, 0x0?, 0x0?)
    /usr/lib/golang/src/runtime/proc.go:424 +0xce fp=0xc000069780 sp=0xc000069760 pc=0x46016e
runtime.goparkunlock(...)
    /usr/lib/golang/src/runtime/proc.go:430
runtime.bgsweep(0xc00002a080)
    /usr/lib/golang/src/runtime/mgcsweep.go:277 +0x94 fp=0xc0000697c8 sp=0xc000069780 pc=0x41f9b4
runtime.gcenable.gowrap1()
    /usr/lib/golang/src/runtime/mgc.go:204 +0x25 fp=0xc0000697e0 sp=0xc0000697c8 pc=0x414325
runtime.goexit({})
    /usr/lib/golang/src/runtime/asm_amd64.s:1700 +0x1 fp=0xc0000697e8 sp=0xc0000697e0 pc=0x4664e1
created by runtime.gcenable in goroutine 1
    /usr/lib/golang/src/runtime/mgc.go:204 +0x66
 
goroutine 4 gp=0xc000007340 m=nil [GC scavenge wait]:
runtime.gopark(0xc00002a080?, 0x49bd40?, 0x1?, 0x0?, 0xc000007340?)
    /usr/lib/golang/src/runtime/proc.go:424 +0xce fp=0xc000069f78 sp=0xc000069f58 pc=0x46016e
runtime.goparkunlock(...)
    /usr/lib/golang/src/runtime/proc.go:430
runtime.(*scavengerState).park(0x4fb080)
    /usr/lib/golang/src/runtime/mgcscavenge.go:425 +0x49 fp=0xc000069fa8 sp=0xc000069f78 pc=0x41d3e9
runtime.bgscavenge(0xc00002a080)
    /usr/lib/golang/src/runtime/mgcscavenge.go:653 +0x3c fp=0xc000069fc8 sp=0xc000069fa8 pc=0x41d95c
runtime.gcenable.gowrap2()
    /usr/lib/golang/src/runtime/mgc.go:205 +0x25 fp=0xc000069fe0 sp=0xc000069fc8 pc=0x4142c5
runtime.goexit({})
    /usr/lib/golang/src/runtime/asm_amd64.s:1700 +0x1 fp=0xc000069fe8 sp=0xc000069fe0 pc=0x4664e1
created by runtime.gcenable in goroutine 1
    /usr/lib/golang/src/runtime/mgc.go:205 +0xa5
 
rax    0x0
rbx    0x16b5bb
rcx    0x7f239b7d2034
rdx    0x6
rdi    0x16b5bb
rsi    0x16b5bb
rbp    0x7ffe3ab331f0
rsp    0x7ffe3ab331b0
r8     0xffffffff
r9     0x0
r10    0x8
r11    0x246
r12    0x7f239b735740
r13    0x7ffe3ab33330
r14    0x6
r15    0x7ffe3ab33330
rip    0x7f239b7d2034
rflags 0x246
cs     0x33
fs     0x0
gs     0x0
exit status 2
Poznámka: hodně štěstí při ladění Go kódu kombinovaného s nativním kódem.

19. Repositář s demonstračními příklady

Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně pět až šest megabajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Příklad Stručný popis Cesta
1 sizeof1.go zjištění velikosti hodnot standardních primitivních datových typů https://github.com/tisnik/go-root/blob/master/unsafe/sizeof1.go
2 sizeof2.go zjištění velikosti polí a řezů (slices) https://github.com/tisnik/go-root/blob/master/unsafe/sizeof2.go
3 sizeof3.go zjištění velikosti hodnot, jejichž typy jsou definovány uživatelskými strukturami https://github.com/tisnik/go-root/blob/master/unsafe/sizeof3.go
4 sizeof4.go zjištění velikosti hodnot nil, které jsou různých typů https://github.com/tisnik/go-root/blob/master/unsafe/sizeof4.go
5 sizeof5.go vliv pořadí prvků ve struktuře na její celkové velikosti https://github.com/tisnik/go-root/blob/master/unsafe/sizeof5.go
6 sizeof6.go vyhodnocení unsafe.Sizeof na konstantu https://github.com/tisnik/go-root/blob/master/unsafe/sizeof6.go
       
7 alignof1.go zjištění zarovnání u základních datových typů https://github.com/tisnik/go-root/blob/master/unsafe/alignof1.go
8 alignof2.go zjištění zarovnání u uživatelských datových struktur https://github.com/tisnik/go-root/blob/master/unsafe/alignof2.go
9 alignof3.go vliv typů prvků struktury na její celkové zarovnání (první varianta příkladu) https://github.com/tisnik/go-root/blob/master/unsafe/alignof3.go
10 alignof4.go vliv typů prvků struktury na její celkové zarovnání (druhá varianta příkladu) https://github.com/tisnik/go-root/blob/master/unsafe/alignof4.go
11 alignof5.go hodnota zarovnání je konstantou https://github.com/tisnik/go-root/blob/master/unsafe/alignof5.go
       
12 offsetof1.go přečtení offsetů jednotlivých prvků datových struktur https://github.com/tisnik/go-root/blob/master/unsafe/offsetof1.go
13 offsetof2.go offsety prvků ve strukturách obsahujících výplňové bajty https://github.com/tisnik/go-root/blob/master/unsafe/offsetof2.go
       
14 unsafe_pointer1.go nízkoúrovňová alokace a dealokace céčkového řetězce https://github.com/tisnik/go-root/blob/master/unsafe/un­safe_pointer1.go
15 unsafe_pointer2.go detekce, zda dojde k OOM https://github.com/tisnik/go-root/blob/master/unsafe/un­safe_pointer2.go
16 unsafe_pointer3.go dealokace paměti v bloku defer https://github.com/tisnik/go-root/blob/master/unsafe/un­safe_pointer3.go
17 unsafe_pointer4.go nekorektní dealokace paměti (na špatném místě) https://github.com/tisnik/go-root/blob/master/unsafe/un­safe_pointer4.go
18 unsafe_pointer5.go dvojí dealokace stejného bloku paměti (double free) https://github.com/tisnik/go-root/blob/master/unsafe/un­safe_pointer5.go
19 unsafe_slice1.go převod Go řetězců na céčkové řetězce a naopak https://github.com/tisnik/go-root/blob/master/unsafe/un­safe_slice1.go
20 unsafe_slice2.go základní způsob využití typu unsafe.Slice https://github.com/tisnik/go-root/blob/master/unsafe/un­safe_slice2.go
21 unsafe_slice3.go další způsob využití typu unsafe.Slice https://github.com/tisnik/go-root/blob/master/unsafe/un­safe_slice3.go

20. Odkazy na Internetu

  1. Unsafe package: documentation
    https://pkg.go.dev/unsafe
  2. An Introduction to Go's unsafe Package: Unsafe Operations
    https://reintech.io/blog/introduction-to-gos-unsafe-package
  3. Go & cgo: integrating existing C code with Go
    http://akrennmair.github.io/golang-cgo-slides/#1
  4. Using cgo to call C code from within Go code
    https://wenzr.wordpress.com/2018/06/07/u­sing-cgo-to-call-c-code-from-within-go-code/
  5. Delve: a debugger for the Go programming language.
    https://github.com/go-delve/delve
  6. Příkazy debuggeru Delve
    https://github.com/go-delve/delve/tree/master/Do­cumentation/cli
  7. Unsafe Go
    https://i0tool5.github.io/le­arnings/golang/going-unsafe/
  8. Unsafe Package Usage in Go
    https://go-cookbook.com/snippets/standard-library-packages/unsafe-package
  9. Padding & Alignment in Go
    https://bitstack.substack­.com/p/padding-and-alignment-in-go
  10. How to Use Unsafe in Go Without Killing Your Service
    https://dev.to/devflex-pro/how-to-use-unsafe-in-go-without-killing-your-service-699
  11. Understanding Struct Padding in Go: In-Depth Guide
    https://kushallabs.com/understanding-struct-padding-in-go-in-depth-guide-ed70c0432c63

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.