Stack'teki yapıların yığın tahsisine karşı yığını ve bunların çöp toplama ile ilişkisi


165

Gitmek için yeniyim ve otomatik değişkenlerin yığın üzerinde yaşadığı ve ayrılan belleğin yığın üzerinde yaşadığı C stili yığın tabanlı programlama ile burada Python tarzı yığın tabanlı programlama yığın üzerinde yaşayan tek şey öbek üzerindeki nesnelere referanslar / işaretçilerdir.

Anlayabildiğim kadarıyla, aşağıdaki iki fonksiyon aynı çıktıyı verir:

func myFunction() (*MyStructType, error) {
    var chunk *MyStructType = new(HeaderChunk)

    ...

    return chunk, nil
}


func myFunction() (*MyStructType, error) {
    var chunk MyStructType

    ...

    return &chunk, nil
}

yani yeni bir yapı tahsis edin ve geri gönderin.

Bunu C'de yazsaydım, birincisi öbeğe bir nesne ve ikincisi yığına koyardı. Birincisi yığına bir işaretçi döndürür, ikincisi yığına bir işaretçi döndürür, bu da işlevin döndüğü zaman buharlaşırdı, ki bu kötü bir şey olurdu.

Python (veya C # dışında birçok modern dilde) yazsaydım, örnek 2 mümkün olmazdı.

Go çöpünün her iki değeri de topladığını anlıyorum, bu yüzden yukarıdaki formların ikisi de iyi.

Alıntılamak:

C'den farklı olarak, yerel bir değişkenin adresini döndürmenin mükemmel olduğunu unutmayın; değişkenle ilişkili depolama, işlev döndükten sonra da devam eder. Aslında, bileşik bir değişmezin adresini almak her değerlendirildiğinde yeni bir örnek tahsis eder, böylece bu son iki satırı birleştirebiliriz.

http://golang.org/doc/effective_go.html#functions

Ama birkaç soru ortaya çıkarıyor.

1 - Örnek 1'de, yapı yığın üzerinde bildirilmiştir. Örnek 2 ne olacak? Bu, yığında C ile aynı şekilde mi bildirilir yoksa öbeye de gider mi?

2 - Yığın üzerinde örnek 2 bildirilirse, işlev döndükten sonra nasıl kullanılabilir kalır?

3 - Örnek 2 gerçekten öbek üzerinde bildirilirse, yapıların referanstan ziyade değere göre aktarılması nasıl olur? Bu durumda işaretçiler ne anlama geliyor?

Yanıtlar:


170

"Yığın" ve "yığın" kelimelerinin dil spesifikasyonunun hiçbir yerinde görünmediğini belirtmek gerekir. Sorunuz "... yığın üzerinde bildirildi" ve "... yığın üzerinde bildirildi" şeklinde ifade edilir, ancak Go bildirimi sözdiziminin yığın veya yığın hakkında hiçbir şey söylemediğini unutmayın.

Bu, teknik olarak tüm sorularınızın uygulanmasına cevap verir. Aslında, bir yığın (goroutine başına!) Ve bir yığın var ve bazı şeyler yığın üzerinde ve bazıları yığın üzerinde gidiyor. Bazı durumlarda derleyici katı kuralları (" newher zaman yığın üzerinde ayırır" gibi) ve diğerlerinde derleyici bir nesnenin yığın üzerinde yaşayabileceğini veya yığın üzerinde tahsis edilmesi gerekip gerekmediğini belirlemek için "kaçış analizi" yapar.

Örnek 2'de, kaçış analizi kaçan yapıya işaretçi gösterecek ve böylece derleyici yapıyı tahsis etmek zorunda kalacaktır. Bence bu durumda Go'nun şu anki uygulaması katı bir kurala uyuyor;

Soru 3 için, terminoloji konusunda kafanız karışabilir. Go'daki her şey değere göre geçer, referansla geçiş yoktur. Burada bir işaretçi değeri döndürüyorsunuz. Göstericilerin anlamı nedir? Örneğinizde aşağıdaki değişikliği göz önünde bulundurun:

type MyStructType struct{}

func myFunction1() (*MyStructType, error) {
    var chunk *MyStructType = new(MyStructType)
    // ...
    return chunk, nil
}

func myFunction2() (MyStructType, error) {
    var chunk MyStructType
    // ...
    return chunk, nil
}

type bigStruct struct {
    lots [1e6]float64
}

func myFunction3() (bigStruct, error) {
    var chunk bigStruct
    // ...
    return chunk, nil
}

Yapım adresi yerine yapıyı döndürmek için myFunction2 değiştirdim. MyFunction1 ve myFunction2 derleme çıktısını şimdi karşılaştırın,

--- prog list "myFunction1" ---
0000 (s.go:5) TEXT    myFunction1+0(SB),$16-24
0001 (s.go:6) MOVQ    $type."".MyStructType+0(SB),(SP)
0002 (s.go:6) CALL    ,runtime.new+0(SB)
0003 (s.go:6) MOVQ    8(SP),AX
0004 (s.go:8) MOVQ    AX,.noname+0(FP)
0005 (s.go:8) MOVQ    $0,.noname+8(FP)
0006 (s.go:8) MOVQ    $0,.noname+16(FP)
0007 (s.go:8) RET     ,

--- prog list "myFunction2" ---
0008 (s.go:11) TEXT    myFunction2+0(SB),$0-16
0009 (s.go:12) LEAQ    chunk+0(SP),DI
0010 (s.go:12) MOVQ    $0,AX
0011 (s.go:14) LEAQ    .noname+0(FP),BX
0012 (s.go:14) LEAQ    chunk+0(SP),BX
0013 (s.go:14) MOVQ    $0,.noname+0(FP)
0014 (s.go:14) MOVQ    $0,.noname+8(FP)
0015 (s.go:14) RET     ,

Buradaki myFunction1 çıkışının peterSO'nun (mükemmel) cevabından farklı olduğundan endişelenmeyin. Açıkçası farklı derleyiciler çalıştırıyoruz. Aksi takdirde, bkz * myStructType yerine myStructType döndürmek için myFunction2 değiştirildi bakın. Runtime.new çağrısı gitti, bu bazı durumlarda iyi bir şey olurdu. Yine de bekle, işte myFunction3,

--- prog list "myFunction3" ---
0016 (s.go:21) TEXT    myFunction3+0(SB),$8000000-8000016
0017 (s.go:22) LEAQ    chunk+-8000000(SP),DI
0018 (s.go:22) MOVQ    $0,AX
0019 (s.go:22) MOVQ    $1000000,CX
0020 (s.go:22) REP     ,
0021 (s.go:22) STOSQ   ,
0022 (s.go:24) LEAQ    chunk+-8000000(SP),SI
0023 (s.go:24) LEAQ    .noname+0(FP),DI
0024 (s.go:24) MOVQ    $1000000,CX
0025 (s.go:24) REP     ,
0026 (s.go:24) MOVSQ   ,
0027 (s.go:24) MOVQ    $0,.noname+8000000(FP)
0028 (s.go:24) MOVQ    $0,.noname+8000008(FP)
0029 (s.go:24) RET     ,

Hala runtime.new çağrısı yok ve evet gerçekten 8MB'lık bir nesneyi değere döndürmek için çalışıyor. Çalışır, ancak genellikle istemezsiniz. Buradaki bir işaretçinin amacı 8MB'lık nesnelerin itilmesinden kaçınmak olacaktır.


9
Mükemmel teşekkürler. Gerçekten "işaretçilerin anlamı nedir" diye sormuyordum, daha çok "değerler işaretçiler gibi göründüğünde işaretçilerin anlamı nedir" diye sormuyordum ve bu durum yine de cevabınız tarafından tartışıldı.
Joe

25
Meclisin kısa bir açıklaması takdir edilecektir.
ElefEnt

59
type MyStructType struct{}

func myFunction1() (*MyStructType, error) {
    var chunk *MyStructType = new(MyStructType)
    // ...
    return chunk, nil
}

func myFunction2() (*MyStructType, error) {
    var chunk MyStructType
    // ...
    return &chunk, nil
}

Her iki durumda da, Go'nun şu anki uygulamaları , bir yığın üzerinde bir structtür için bellek ayırır MyStructTypeve adresini döndürür. Fonksiyonlar eşdeğerdir; derleyici asm kaynağı aynıdır.

--- prog list "myFunction1" ---
0000 (temp.go:9) TEXT    myFunction1+0(SB),$8-12
0001 (temp.go:10) MOVL    $type."".MyStructType+0(SB),(SP)
0002 (temp.go:10) CALL    ,runtime.new+0(SB)
0003 (temp.go:10) MOVL    4(SP),BX
0004 (temp.go:12) MOVL    BX,.noname+0(FP)
0005 (temp.go:12) MOVL    $0,AX
0006 (temp.go:12) LEAL    .noname+4(FP),DI
0007 (temp.go:12) STOSL   ,
0008 (temp.go:12) STOSL   ,
0009 (temp.go:12) RET     ,

--- prog list "myFunction2" ---
0010 (temp.go:15) TEXT    myFunction2+0(SB),$8-12
0011 (temp.go:16) MOVL    $type."".MyStructType+0(SB),(SP)
0012 (temp.go:16) CALL    ,runtime.new+0(SB)
0013 (temp.go:16) MOVL    4(SP),BX
0014 (temp.go:18) MOVL    BX,.noname+0(FP)
0015 (temp.go:18) MOVL    $0,AX
0016 (temp.go:18) LEAL    .noname+4(FP),DI
0017 (temp.go:18) STOSL   ,
0018 (temp.go:18) STOSL   ,
0019 (temp.go:18) RET     ,

Aramalar

Bir işlev çağrısında, işlev değeri ve bağımsız değişkenler olağan sırada değerlendirilir. Değerlendirildikten sonra, çağrının parametreleri değere göre işleve iletilir ve çağrılan işlev yürütülmeye başlar. İşlevin dönüş parametreleri, işlev geri döndüğünde değere göre çağrı işlevine iletilir.

Tüm işlev ve dönüş parametreleri değere göre iletilir. Type ile döndürülen parametre değeri *MyStructTypebir adrestir.


Çok teşekkürler! Oy verildi, ama ben kaçış analizi hakkında biraz yüzünden Sonia'nın kabul ediyorum.
Joe

1
peterSo, sen ve @Sonia bu montajı nasıl üretiyorsunuz? Her ikiniz de aynı biçimlendirmeye sahip. Komut / bayraklardan bağımsız olarak objdump, go tool, otool denemeden üretemiyorum.
10 cls

3
Ah, anladım - gcflags.
10 cls 10

30

Go'nun SSS'sine göre :

derleyici işlev döndükten sonra değişkene başvurulmadığını ispatlayamazsa, derleyici değişkenin çöp toplanmış yığın üzerinde ayrılmasını ve işaretçi hatalarının sarkmasını önler.



0
func Function1() (*MyStructType, error) {
    var chunk *MyStructType = new(HeaderChunk)

    ...

    return chunk, nil
}


func Function2() (*MyStructType, error) {
    var chunk MyStructType

    ...

    return &chunk, nil
}

İşlev1 ve İşlev2 satır içi işlev olabilir. Ve dönüş değişkeni kaçmayacak. Öbek üzerinde değişken ayırmaya gerek yoktur.

Örnek kodum:

 1  package main
 2  
 3  type S struct {
 4          x int
 5  }
 6  
 7  func main() {
 8          F1()
 9          F2()
10          F3()
11  }
12  
13  func F1() *S {
14          s := new(S)
15          return s
16  }
17  
18  func F2() *S {
19          s := S{x: 10}
20          return &s
21  }
22  
23  func F3() S {
24          s := S{x: 9}
25          return s
26  }

Cmd çıktısına göre:

go run -gcflags -m test.go

çıktı:

# command-line-arguments
./test.go:13:6: can inline F1
./test.go:18:6: can inline F2
./test.go:23:6: can inline F3
./test.go:7:6: can inline main
./test.go:8:4: inlining call to F1
./test.go:9:4: inlining call to F2
./test.go:10:4: inlining call to F3
/var/folders/nr/lxtqsz6x1x1gfbyp1p0jy4p00000gn/T/go-build333003258/b001/_gomod_.go:6:6: can inline init.0
./test.go:8:4: main new(S) does not escape
./test.go:9:4: main &s does not escape
./test.go:14:10: new(S) escapes to heap
./test.go:20:9: &s escapes to heap
./test.go:19:2: moved to heap: s

Derleyici yeterince akıllıysa, F1 () F2 () F3 () çağırılamayabilir. Çünkü hiçbir anlamı yok.

Bir değişkenin yığın veya yığına ayrılıp ayrılmadığına aldırmayın, sadece kullanın. Gerekirse muteks veya kanal ile koruyun.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.