गो में स्ट्रक्च बनाम ढेर आवंटन का ढेर, और वे कचरा संग्रहण से कैसे संबंधित हैं


165

मैं जाने के लिए नया हूं और मैं सी-स्टाइल स्टैक-आधारित प्रोग्रामिंग के बीच थोड़ा सा कॉन्सेप्टिव असंगति का अनुभव कर रहा हूं, जहां ढेर पर स्वचालित चर रहते हैं और ढेर और पाइथन-स्टाइल स्टैक-आधारित-प्रोग्रामिंग पर आवंटित मेमोरी रहती है। केवल एक चीज जो स्टैक पर रहती है वह है ढेर पर मौजूद वस्तुओं के संदर्भ / संकेत।

जहाँ तक मैं बता सकता हूँ, निम्नलिखित दो कार्य समान आउटपुट देते हैं:

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

    ...

    return chunk, nil
}


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

    ...

    return &chunk, nil
}

यानी एक नई संरचना आवंटित करें और उसे वापस लौटाएं।

अगर मैंने लिखा है कि सी में, पहले एक ने ढेर पर एक वस्तु रखी होगी और दूसरी ने उसे ढेर पर रखा होगा। पहला ढेर के लिए एक पॉइंटर लौटाएगा, दूसरा एक पॉइंटर को स्टैक पर लौटाएगा, जो कि फ़ंक्शन के वापस आने तक वाष्पित हो जाएगा, जो एक बैड थिंग होगा।

अगर मैंने इसे पायथन (या C # को छोड़कर कई अन्य आधुनिक भाषाओं में लिखा) उदाहरण 2 संभव नहीं होता।

मुझे लगता है कि गो कचरा दोनों मूल्यों को इकट्ठा करता है, इसलिए उपरोक्त दोनों रूप ठीक हैं।

उद्धरण के लिए:

ध्यान दें कि, सी के विपरीत, स्थानीय चर का पता वापस करने के लिए यह पूरी तरह से ठीक है; वेरिएबल से जुड़ा स्टोरेज फंक्शन के वापस आने के बाद बच जाता है। वास्तव में, एक समग्र शाब्दिक का पता लेने से प्रत्येक बार मूल्यांकन किए जाने पर एक ताजा उदाहरण आवंटित होता है, इसलिए हम इन अंतिम दो पंक्तियों को जोड़ सकते हैं।

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

लेकिन यह कुछ सवालों को उठाता है।

1 - उदाहरण 1 में, संरचना ढेर पर घोषित की गई है। उदाहरण 2 के बारे में क्या? क्या इसे स्टैक पर उसी तरह से घोषित किया जाता है जैसे यह C में होता है या यह ढेर पर भी जाता है?

2 - यदि उदाहरण 2 को स्टैक पर घोषित किया जाता है, तो फ़ंक्शन रिटर्न के बाद यह कैसे उपलब्ध रहता है?

3 - यदि उदाहरण 2 वास्तव में ढेर पर घोषित किया गया है, तो यह कैसे है कि संदर्भ के बजाय मूल्य द्वारा संरचनाएं पारित की जाती हैं? इस मामले में संकेत देने वाली बात क्या है?

जवाबों:


170

यह ध्यान देने योग्य है कि "स्टैक" और "हीप" शब्द भाषा की कल्पना में कहीं भी दिखाई नहीं देते हैं। आपका प्रश्न "... स्टैक पर घोषित है," और "... हीप पर घोषित" है, लेकिन ध्यान दें कि गो घोषणा सिंटैक्स स्टैक या हीप के बारे में कुछ नहीं कहता है।

यह तकनीकी रूप से आपके सभी प्रश्नों के उत्तर को निर्भर बनाता है। वास्तव में, एक स्टैक (प्रति गोरोइन!) और एक ढेर है और कुछ चीजें स्टैक पर और कुछ ढेर पर जाती हैं। कुछ मामलों में कंपाइलर कठोर नियमों का पालन करता है (जैसे " newहमेशा ढेर पर आवंटित होता है") और दूसरों में यह तय करने के लिए कंपाइलर "एस्केप एनालिसिस" करता है कि क्या कोई ऑब्जेक्ट स्टैक पर रह सकता है या यदि उसे हीप पर आवंटित किया जाना चाहिए।

आपके उदाहरण 2 में, एस्केप एनालिसिस पॉइंटर से बचने के लिए पॉकेट दिखाएगा और इसलिए कंपाइलर को स्ट्रक्चर को आवंटित करना होगा। मुझे लगता है कि गो का वर्तमान कार्यान्वयन इस मामले में एक कठोर नियम का पालन करता है, जो यह है कि यदि पता किसी संरचना के किसी हिस्से से लिया जाता है, तो संरचना ढेर पर चली जाती है।

प्रश्न 3 के लिए, हम शब्दावली के बारे में भ्रमित होने का जोखिम उठाते हैं। गो में सब कुछ मूल्य से पारित किया गया है, संदर्भ से कोई पास नहीं है। यहां आप एक पॉइंटर मान लौटा रहे हैं। क्या कहना है पॉइंटर्स का? अपने उदाहरण के निम्नलिखित संशोधन पर विचार करें:

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
}

मैंने संरचना के पते के बजाय संरचना को वापस करने के लिए myFunction2 को संशोधित किया। MyFunction1 और myFunction2 के असेंबली आउटपुट की तुलना करें अब,

--- 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     ,

चिंता न करें कि यहाँ myFunction1 आउटपुट, peterSO के (उत्कृष्ट) उत्तर से भिन्न है। हम स्पष्ट रूप से विभिन्न संकलक चला रहे हैं। अन्यथा, देखें कि मैंने * myStructType के बजाय myStructType को वापस करने के लिए myFunction2 को संशोधित किया है। Runtime.new पर कॉल चला गया है, जो कुछ मामलों में एक अच्छी बात होगी। हालाँकि, यहाँ पर रखें 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     ,

फिर भी runtime.new के लिए कोई कॉल नहीं है, और हाँ यह वास्तव में मूल्य द्वारा 8MB ऑब्जेक्ट को वापस करने के लिए काम करता है। यह काम करता है, लेकिन आप आमतौर पर नहीं करना चाहेंगे। यहाँ एक पॉइंटर की बात 8MB ऑब्जेक्ट्स को पुश करने से बचने के लिए होगी।


9
उत्कृष्ट धन्यवाद। मैं वास्तव में यह नहीं पूछ रहा था कि "बिंदुओं का क्या मतलब है", यह अधिक पसंद था "क्या संकेत देने वालों की बात है जब मूल्य बिंदुओं की तरह व्यवहार करते दिखाई देते हैं", और उस मामले को वैसे भी आपके जवाब से लूट लिया जाता है।
जो

25
विधानसभा की एक छोटी व्याख्या की सराहना की जाएगी।
एलिफेंटेंट

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
}

दोनों मामलों में, गो के वर्तमान कार्यान्वयन ढेर पर structप्रकार के लिए मेमोरी आवंटित करेंगे MyStructTypeऔर अपना पता वापस करेंगे। कार्य समतुल्य हैं; संकलक asm स्रोत समान है।

--- 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     ,

कॉल

फ़ंक्शन कॉल में, फ़ंक्शन मान और तर्कों का सामान्य क्रम में मूल्यांकन किया जाता है। जब उनका मूल्यांकन किया जाता है, तो कॉल के मापदंडों को फ़ंक्शन द्वारा मान से पारित किया जाता है और बुलाया फ़ंक्शन निष्पादन शुरू करता है। फ़ंक्शन के रिटर्न मापदंडों को फ़ंक्शन वापस आने पर कॉलिंग फ़ंक्शन पर वापस मान द्वारा पारित किया जाता है।

सभी फ़ंक्शन और रिटर्न पैरामीटर मान द्वारा पारित किए जाते हैं। प्रकार के साथ वापसी पैरामीटर मान *MyStructTypeएक पता है।


बहुत बहुत धन्यवाद! अपटेड, लेकिन मैं सोनिया के भागने के विश्लेषण के बारे में थोड़ा सा स्वीकार कर रहा हूं।
जो

1
peterSo, आप और @ सोनिया उस विधानसभा का निर्माण कैसे कर रहे हैं? आप दोनों का प्रारूप एक जैसा है। मैं आदेश / झंडे की परवाह किए बिना इसका उत्पादन नहीं कर सकता, कोशिश की, objdump, उपकरण जाओ, otool।
10

3
आह, समझ गया - gcflags।
10 cls

30

गो के अकसर किये गए सवाल के अनुसार :

यदि कंपाइलर यह साबित नहीं कर सकता है कि फ़ंक्शन वापस आने के बाद वेरिएबल को संदर्भित नहीं किया गया है, तो कंपाइलर को गारंटर-एकत्रित ढेर पर वेरिएबल को पॉइंटर पॉइंटर त्रुटियों से बचने के लिए आवंटित करना होगा।


11

आपको हमेशा यह नहीं पता होता है कि आपका वेरिएबल स्टैक या हीप पर आबंटित है या नहीं।
...
यदि आपको यह जानने की आवश्यकता है कि आपके चर कहां आवंटित किए गए हैं, तो "गो बिल्ड" या "गो रन" (जैसे, go run -gcflags -m app.go) के लिए "-m" जीसी फ्लैग पास करें ।

स्रोत: http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index.html#stack_heap_vars


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

    ...

    return chunk, nil
}


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

    ...

    return &chunk, nil
}

फ़ंक्शन 1 और फ़ंक्शन 2 इनलाइन फ़ंक्शन हो सकते हैं। और रिटर्न वैरिएबल नहीं बचेगा। यह ढेर पर चर आवंटित करने के लिए आवश्यक नहीं है।

मेरा उदाहरण कोड:

 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 के उत्पादन के अनुसार:

go run -gcflags -m test.go

उत्पादन:

# 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

यदि कंपाइलर काफी स्मार्ट है, तो F1 () F2 () F3 () नहीं कहा जा सकता है। क्योंकि इसका कोई साधन नहीं है।

इस बात की परवाह न करें कि क्या ढेर या ढेर पर एक चर आवंटित किया गया है, बस इसका उपयोग करें। यदि आवश्यक हो तो म्यूटेक्स या चैनल द्वारा इसे सुरक्षित रखें।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.