Obsah
1. Využití standardního balíčku „unsafe“ v programovacím jazyku Go
2. Nápověda dostupná pro balíček „unsafe“
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
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
14. Přečtení offsetů jednotlivých prvků datových struktur
15. Offsety prvků ve strukturách obsahujících výplňové bajty
17. Explicitní (ruční) alokace a dealokace paměti
18. Unsafe ukazatele jsou skutečně unsafe
19. Repositář s demonstračními příklady
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ů.
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
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.
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).
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)
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)
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)
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)
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)
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)
}
Již uvolněný blok paměti není možné znovu uvolnit, na což nás může (ale nemusí) upozornit runtime knihovna jazyka C:
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
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/unsafe_pointer1.go |
| 15 | unsafe_pointer2.go | detekce, zda dojde k OOM | https://github.com/tisnik/go-root/blob/master/unsafe/unsafe_pointer2.go |
| 16 | unsafe_pointer3.go | dealokace paměti v bloku defer | https://github.com/tisnik/go-root/blob/master/unsafe/unsafe_pointer3.go |
| 17 | unsafe_pointer4.go | nekorektní dealokace paměti (na špatném místě) | https://github.com/tisnik/go-root/blob/master/unsafe/unsafe_pointer4.go |
| 18 | unsafe_pointer5.go | dvojí dealokace stejného bloku paměti (double free) | https://github.com/tisnik/go-root/blob/master/unsafe/unsafe_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/unsafe_slice1.go |
| 20 | unsafe_slice2.go | základní způsob využití typu unsafe.Slice | https://github.com/tisnik/go-root/blob/master/unsafe/unsafe_slice2.go |
| 21 | unsafe_slice3.go | další způsob využití typu unsafe.Slice | https://github.com/tisnik/go-root/blob/master/unsafe/unsafe_slice3.go |
20. Odkazy na Internetu
- Unsafe package: documentation
https://pkg.go.dev/unsafe - An Introduction to Go's
unsafePackage: Unsafe Operations
https://reintech.io/blog/introduction-to-gos-unsafe-package - Go & cgo: integrating existing C code with Go
http://akrennmair.github.io/golang-cgo-slides/#1 - Using cgo to call C code from within Go code
https://wenzr.wordpress.com/2018/06/07/using-cgo-to-call-c-code-from-within-go-code/ - Delve: a debugger for the Go programming language.
https://github.com/go-delve/delve - Příkazy debuggeru Delve
https://github.com/go-delve/delve/tree/master/Documentation/cli - Unsafe Go
https://i0tool5.github.io/learnings/golang/going-unsafe/ - Unsafe Package Usage in Go
https://go-cookbook.com/snippets/standard-library-packages/unsafe-package - Padding & Alignment in Go
https://bitstack.substack.com/p/padding-and-alignment-in-go - 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 - Understanding Struct Padding in Go: In-Depth Guide
https://kushallabs.com/understanding-struct-padding-in-go-in-depth-guide-ed70c0432c63

