ホームページ  >  記事  >  バックエンド開発  >  Go を復号化: 空の構造体

Go を復号化: 空の構造体

PHPz
PHPzオリジナル
2024-09-11 12:30:32828ブラウズ

Decrypt Go: empty struct

Go では、通常、通常の構造体はメモリのブロックを占有します。ただし、特殊な場合があります。空の構造体の場合、そのサイズはゼロです。これはどのようにして可能ですか?また、空の構造体はどのように使用されるのでしょうか?

この記事は、中型 MPP プランで初めて公開されました。ミディアムユーザーの方はミディアムでフォローしてください。誠にありがとうございます。

type Test struct {
    A int
    B string
}

func main() {
    fmt.Println(unsafe.Sizeof(new(Test)))
    fmt.Println(unsafe.Sizeof(struct{}{}))
}

/*
8
0
*/

空の構造体の秘密

特殊変数: ゼロベース

空の構造体は、メモリ サイズのない構造体です。このステートメントは正しいですが、より正確に言うと、実際には特別な開始点、つまりゼロベース変数があります。これは、8 バイトを占める uintptr グローバル変数です。無数の struct {} 変数が定義されるたびに、コンパイラはこのゼロベース変数のアドレスを割り当てます。言い換えると、Go では、サイズ 0 のメモリ割り当てには同じアドレス &zerobase が使用されます。


package main

import "fmt"

type emptyStruct struct {}

func main() {
    a := struct{}{}
    b := struct{}{}
    c := emptyStruct{}

    fmt.Printf("%p\n", &a)
    fmt.Printf("%p\n", &b)
    fmt.Printf("%p\n", &c)
}

// 0x58e360
// 0x58e360
// 0x58e360

空の構造体の変数のメモリアドレスはすべて同じです。これは、この特殊なタイプのメモリ割り当てが発生すると、コンパイラがコンパイル中に &zerobase を割り当てるためです。このロジックは mallocgc 関数内にあります:

//go:linkname mallocgc  
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {  
    ...
    if size == 0 {  
       return unsafe.Pointer(&zerobase)  
    }
    ...

これが Empty 構造体の秘密です。この特別な変数を使用すると、多くの機能を実現できます。

空の構造体とメモリのアラインメント

通常、空の構造体がより大きな構造体の一部である場合、それはメモリを占有しません。ただし、空の構造体が最後のフィールドである場合は特殊な場合があります。それは記憶の調整を引き起こします。


type A struct {
    x int
    y string
    z struct{}
}
type B struct {
    x int
    z struct{}
    y string
}

func main() {
    println(unsafe.Alignof(A{}))
    println(unsafe.Alignof(B{}))
    println(unsafe.Sizeof(A{}))
    println(unsafe.Sizeof(B{}))
}

/**
8
8
32
24
**/

フィールドへのポインタが存在する場合、返されるアドレスは構造体の外にある可能性があり、構造体の解放時にメモリが解放されないとメモリ リークが発生する可能性があります。したがって、空の構造体が別の構造体の最後のフィールドである場合、安全のために追加のメモリが割り当てられます。空の構造体が先頭または途中にある場合、そのアドレスは次の変数と同じになります。

type A struct {  
    x int  
    y string  
    z struct{}  
}  
type B struct {  
    x int  
    z struct{}  
    y string  
}  

func main() {  
    a := A{}  
    b := B{}  
    fmt.Printf("%p\n", &a.y)  
    fmt.Printf("%p\n", &a.z)  
    fmt.Printf("%p\n", &b.y)  
    fmt.Printf("%p\n", &b.z)  
}

/**
0x1400012c008
0x1400012c018
0x1400012e008
0x1400012e008
**/

空の構造体の使用例

空の構造体 struct{} が存在する主な理由は、メモリを節約することです。構造体が必要だがその内容は気にしない場合は、空の構造体の使用を検討してください。 Map、chan、slice などの Go のコア複合構造はすべて struct{} を使用できます。

マップと構造体{}

// Create map
m := make(map[int]struct{})
// Assign value
m[1] = struct{}{}
// Check if key exists
_, ok := m[1]

チャンと構造体{}

古典的なシナリオでは、チャネルと struct{} が結合されます。ここで、struct{} は、その内容を気にせずにシグナルとして使用されることがよくあります。以前の記事で分析したように、チャネルの基本的なデータ構造は、管理構造とリング バッファーです。 struct{} が要素として使用される場合、リング バッファーはゼロ割り当てされます。

空の構造体自体は値を運ぶことができないため、chan と struct{} を一緒に使用するのは 信号送信 のみです。通常、バッファチャネルなしで使用されます。

// Create a signal channel
waitc := make(chan struct{})

// ...
goroutine 1:
    // Send signal: push element
    waitc <- struct{}{}
    // Send signal: close
    close(waitc)

goroutine 2:
    select {
    // Receive signal and perform corresponding actions
    case <-waitc:
    }    

このシナリオでは、struct{} は絶対に必要ですか?実際にはそうではなく、節約されるメモリはごくわずかです。重要な点は、chan の要素値は考慮されないため、struct{} が使用されるということです。

まとめ

  1. 空の構造体は、サイズが 0 であるだけで、依然として構造体です。
  2. すべての空の構造体は同じアドレス、つまりゼロベースのアドレスを共有します。
  3. 空の構造体の非メモリ占有機能を利用して、マップを使用してセットやチャネルを実装するなど、コードを最適化できます。

参考文献

  1. 空の構造体、Dave Cheney
  2. Go 最细节篇— struct{} 空结构体究竟是啥?

以上がGo を復号化: 空の構造体の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。