อาร์เรย์ฮีปและสแต็กและชนิดของค่า


136
int[] myIntegers;
myIntegers = new int[100];

ในโค้ดด้านบน int [100] ใหม่กำลังสร้างอาร์เรย์บนฮีปหรือไม่ จากสิ่งที่ฉันได้อ่านเกี่ยวกับ CLR ผ่าน c # คำตอบคือใช่ แต่สิ่งที่ฉันไม่เข้าใจคือสิ่งที่เกิดขึ้นกับ int จริงภายในอาร์เรย์ เนื่องจากเป็นประเภทค่าฉันจึงเดาว่าพวกเขาจะต้องอยู่ในกล่องอย่างที่ฉันทำได้เช่นส่ง myIntegers ไปยังส่วนอื่น ๆ ของโปรแกรมและมันจะทำให้สแต็คยุ่งเหยิงหากพวกเขาถูกทิ้งไว้ตลอดเวลา . หรือว่าฉันผิด? ฉันเดาว่าพวกมันเพิ่งถูกบรรจุกล่องและจะอยู่บนกองได้นานเท่าที่อาร์เรย์มี

คำตอบ:


294

อาร์เรย์ของคุณได้รับการจัดสรรบนฮีปและ ints ไม่ได้อยู่ในกล่อง

ที่มาของความสับสนของคุณน่าจะเป็นเพราะมีคนบอกว่ามีการจัดสรรประเภทการอ้างอิงบนฮีปและประเภทค่าจะถูกจัดสรรบนสแต็ก นี่ไม่ใช่การนำเสนอที่ถูกต้องทั้งหมด

ตัวแปรโลคัลและพารามิเตอร์ทั้งหมดถูกจัดสรรบนสแตก ซึ่งรวมถึงประเภทค่าและประเภทการอ้างอิง ความแตกต่างระหว่างทั้งสองเป็นเพียงสิ่งที่เก็บไว้ในตัวแปรเท่านั้น ไม่น่าแปลกใจสำหรับประเภทค่าค่าของประเภทจะถูกเก็บไว้ในตัวแปรโดยตรงและสำหรับประเภทการอ้างอิงค่าของประเภทจะถูกเก็บไว้ในฮีปและการอ้างอิงถึงค่านี้คือสิ่งที่เก็บไว้ในตัวแปร

เช่นเดียวกันกับเขตข้อมูล เมื่อหน่วยความจำถูกจัดสรรสำหรับอินสแตนซ์ประเภทการรวม (a classหรือ a struct) หน่วยความจำจะต้องรวมหน่วยเก็บข้อมูลสำหรับฟิลด์อินสแตนซ์แต่ละช่อง สำหรับฟิลด์ประเภทการอ้างอิงที่เก็บข้อมูลนี้จะเก็บเพียงการอ้างอิงถึงค่าซึ่งจะถูกจัดสรรบนฮีปในภายหลัง สำหรับฟิลด์ชนิดค่าที่เก็บข้อมูลนี้จะเก็บค่าจริง

ดังนั้นกำหนดประเภทต่อไปนี้:

class RefType{
    public int    I;
    public string S;
    public long   L;
}

struct ValType{
    public int    I;
    public string S;
    public long   L;
}

ค่าของแต่ละประเภทเหล่านี้จะต้องใช้หน่วยความจำ 16 ไบต์ (สมมติว่าเป็นขนาดคำ 32 บิต) ฟิลด์Iในแต่ละกรณีใช้เวลา 4 ไบต์ในการจัดเก็บค่าฟิลด์นี้Sใช้เวลา 4 ไบต์ในการจัดเก็บข้อมูลอ้างอิงและฟิลด์นี้Lจะใช้ 8 ไบต์ในการจัดเก็บค่า ดังนั้นหน่วยความจำสำหรับค่าของทั้งสองRefTypeและValTypeมีลักษณะดังนี้:

 0 ┌───────────────────┐
   │ฉัน│
 4 ├───────────────────┤
   │ S │
 8 ├───────────────────┤
   │ L │
   ││
16 └───────────────────┘

ตอนนี้ถ้าคุณมีสามตัวแปรท้องถิ่นในการทำงาน, ประเภทRefType, ValTypeและint[]เช่นนี้

RefType refType;
ValType valType;
int[]   intArray;

สแต็กของคุณอาจมีลักษณะดังนี้:

 0 ┌───────────────────┐
   │ refType │
 4 ├───────────────────┤
   │ valType │
   ││
   ││
   ││
20 ├───────────────────┤
   │ intArray │
24 วินาที

หากคุณกำหนดค่าให้กับตัวแปรโลคัลเหล่านี้ดังนี้:

refType = new RefType();
refType.I = 100;
refType.S = "refType.S";
refType.L = 0x0123456789ABCDEF;

valType = new ValType();
valType.I = 200;
valType.S = "valType.S";
valType.L = 0x0011223344556677;

intArray = new int[4];
intArray[0] = 300;
intArray[1] = 301;
intArray[2] = 302;
intArray[3] = 303;

จากนั้นกองของคุณอาจมีลักษณะดังนี้:

 0 ┌───────────────────┐
   │ 0x4A963B68 │ - ที่อยู่ฮีปของ "refType"
 4 ├───────────────────┤
   │ 200 │ - ค่าของ "valType.I`
   │ 0x4A984C10 │ - ที่อยู่ฮีปของ "valType.S`
   │ 0x44556677 │ - "valType.L` ต่ำ 32 บิต
   │ 0x00112233 │ - สูง 32 บิตของ "valType.L`
20 ├───────────────────┤
   │ 0x4AA4C288 │ - ที่อยู่ฮีปของʻintArray`
24 วินาที

หน่วยความจำที่อยู่0x4A963B68(ค่าของrefType) จะเป็นดังนี้:

 0 ┌───────────────────┐
   │ 100 │ - ค่าของ "refType.I`
 4 ├───────────────────┤
   │ 0x4A984D88 │ - ที่อยู่ฮีปของ "refType.S`
 8 ├───────────────────┤
   │ 0x89ABCDEF │ - ต่ำ 32 บิตของ "refType.L`
   │ 0x01234567 │ - สูง 32 บิตของ "refType.L`
16 └───────────────────┘

หน่วยความจำที่อยู่0x4AA4C288(ค่าของintArray) จะเป็นดังนี้:

 0 ┌───────────────────┐
   │ 4 │ - ความยาวของอาร์เรย์
 4 ├───────────────────┤
   │ 300 │ -ʻintArray [0] `
 8 ├───────────────────┤
   │ 301 │ -ʻintArray [1] `
12 ├───────────────────┤
   │ 302 │ -ʻintArray [2] `
16 ├───────────────────┤
   │ 303 │ -ʻintArray [3] `
20 └───────────────────┘

ตอนนี้ถ้าคุณส่งผ่านintArrayไปยังฟังก์ชันอื่นค่าที่ส่งไปยังสแต็กจะเป็น0x4AA4C288ที่อยู่ของอาร์เรย์ไม่ใช่สำเนาของอาร์เรย์


52
ฉันทราบว่าข้อความที่ระบุว่าตัวแปรภายในทั้งหมดถูกเก็บไว้ในสแต็กนั้นไม่ถูกต้อง ตัวแปรโลคัลที่เป็นตัวแปรภายนอกของฟังก์ชันที่ไม่ระบุชื่อจะถูกเก็บไว้ในฮีป ตัวแปรโลคัลของบล็อกตัววนซ้ำจะถูกเก็บไว้บนฮีป ตัวแปรโลคัลของบล็อก async จะถูกเก็บไว้ในฮีป ตัวแปรโลคัลที่ลงทะเบียนไว้จะไม่ถูกเก็บไว้ในสแต็กหรือฮีป ตัวแปรโลคัลที่ถูกแยกออกจะไม่ถูกเก็บไว้ในสแต็กหรือฮีป
Eric Lippert

5
ฮ่า ๆ นายลิปเพิร์ทช่างเลือกเสมอ :) ฉันรู้สึกว่าจำเป็นที่จะต้องชี้ให้เห็นว่ายกเว้นสองกรณีหลังของคุณสิ่งที่เรียกว่า "คนในพื้นที่" จะไม่เป็นคนในพื้นที่ในเวลารวบรวม การนำไปใช้ทำให้พวกเขามีสถานะเป็นสมาชิกชั้นเรียนซึ่งเป็นเหตุผลเดียวที่ทำให้พวกเขาถูกเก็บไว้ในฮีป ดังนั้นจึงเป็นเพียงรายละเอียดการใช้งานเท่านั้น (snicker) แน่นอนว่าการลงทะเบียนพื้นที่เก็บข้อมูลเป็นรายละเอียดการใช้งานในระดับที่ต่ำกว่าและไม่นับการคัดแยก
P Daddy

3
แน่นอนว่าโพสต์ทั้งหมดของฉันเป็นรายละเอียดการนำไปใช้งาน แต่อย่างที่ฉันแน่ใจว่าคุณทราบดีว่าทั้งหมดนี้เป็นความพยายามที่จะแยกแนวคิดของตัวแปรและค่าต่างๆ ตัวแปร (เรียกว่าโลคัลฟิลด์พารามิเตอร์อะไรก็ได้) สามารถเก็บไว้ในสแต็กฮีปหรือตำแหน่งอื่น ๆ ที่กำหนดการนำไปใช้งานได้ แต่นั่นไม่ใช่สิ่งที่สำคัญจริงๆ สิ่งที่สำคัญคือตัวแปรนั้นเก็บค่าที่แสดงไว้โดยตรงหรือเพียงแค่การอ้างอิงถึงค่านั้นเก็บไว้ที่อื่น เป็นสิ่งสำคัญเนื่องจากมีผลต่อความหมายของการคัดลอกไม่ว่าการคัดลอกตัวแปรนั้นจะคัดลอกค่าหรือที่อยู่ของตัวแปร
P Daddy

16
เห็นได้ชัดว่าคุณมีความคิดที่แตกต่างจากความหมายของการเป็น "ตัวแปรท้องถิ่น" มากกว่าที่ฉันคิด คุณดูเหมือนจะเชื่อว่าเป็น "ตัวแปรท้องถิ่น" เป็นลักษณะของรายละเอียดการดำเนินงาน ความเชื่อนี้ไม่ได้รับการพิสูจน์จากสิ่งใด ๆ ที่ฉันทราบในข้อกำหนด C # ตัวแปรโลคัลเป็นตัวแปรที่ประกาศภายในบล็อกที่มีชื่ออยู่ในขอบเขตเฉพาะตลอดพื้นที่การประกาศที่เกี่ยวข้องกับบล็อก ฉันขอรับรองว่าตัวแปรโลคัลที่เป็นรายละเอียดการนำไปใช้งานถูกยกไปยังฟิลด์ของคลาสการปิดยังคงเป็นตัวแปรโลคัลตามกฎของ C #
Eric Lippert

15
แน่นอนว่าคำตอบของคุณนั้นยอดเยี่ยมมาก ประเด็นที่ว่าค่ามีแนวคิดแตกต่างจากตัวแปรคือสิ่งที่ต้องทำให้บ่อยและดังที่สุดเนื่องจากเป็นพื้นฐาน และยังมีคนจำนวนมากเชื่อตำนานที่แปลกประหลาดเกี่ยวกับพวกเขา! ดีสำหรับคุณในการต่อสู้การต่อสู้ที่ดี
Eric Lippert

23

ใช่อาร์เรย์จะอยู่บนฮีป

ints ภายในอาร์เรย์จะไม่อยู่ในกล่อง เนื่องจากประเภทค่ามีอยู่ในฮีปไม่ได้แปลว่าจะอยู่ในกล่องเสมอไป การชกมวยจะเกิดขึ้นก็ต่อเมื่อประเภทค่าเช่น int ถูกกำหนดให้กับการอ้างอิงของออบเจ็กต์ประเภท

ตัวอย่างเช่น

ไม่มีกล่อง:

int i = 42;
myIntegers[0] = 42;

กล่อง:

object i = 42;
object[] arr = new object[10];  // no boxing here 
arr[0] = 42;

คุณอาจต้องการตรวจสอบโพสต์ของ Eric ในหัวข้อนี้:


1
แต่ฉันไม่เข้าใจ ไม่ควรจัดสรรชนิดค่าบนสแตก? หรือทั้งค่าและประเภทอ้างอิงสามารถจัดสรรได้ทั้งบนฮีปหรือสแต็กและโดยปกติแล้วค่าเหล่านี้จะถูกเก็บไว้ในที่เดียวหรือที่อื่น ๆ ?
กินเอลิเซียม

4
@Jorge ประเภทของค่าที่ไม่มีชนิดอ้างอิง wrapper / container จะอยู่บนสแต็ก อย่างไรก็ตามเมื่อใช้ภายในคอนเทนเนอร์ประเภทอ้างอิงแล้วจะอยู่ในฮีป อาร์เรย์เป็นชนิดการอ้างอิงดังนั้นหน่วยความจำสำหรับ int จึงต้องอยู่ในฮีป
JaredPar

2
@Jorge: ประเภทการอ้างอิงอาศัยอยู่ในฮีปเท่านั้นไม่อยู่ในสแต็ก ในทางตรงกันข้ามเป็นไปไม่ได้ (ในรหัสที่ตรวจสอบได้) ที่จะจัดเก็บตัวชี้ไปยังตำแหน่งสแต็กในวัตถุประเภทการอ้างอิง
Anton Tykhyy

1
ฉันคิดว่าคุณตั้งใจจะมอบหมายให้ i เป็น arr [0] การกำหนดคงที่จะยังคงทำให้เกิดการชกมวยของ "42" แต่คุณสร้าง i ขึ้นมาดังนั้นคุณสามารถใช้มันได้เช่นกัน ;-)
Marcus Griep

@AntonTykhyy: ไม่มีกฎที่ฉันรู้ว่า CLR ไม่สามารถหลีกเลี่ยงการวิเคราะห์ได้ หากตรวจพบว่าอ็อบเจ็กต์จะไม่ถูกอ้างอิงเลยตลอดอายุการใช้งานของฟังก์ชันที่สร้างขึ้นการสร้างอ็อบเจกต์บนสแต็กนั้นถูกต้องตามกฎหมายโดยสิ้นเชิงไม่ว่าจะเป็นประเภทค่าหรือไม่ก็ตาม "ประเภทค่า" และ "ประเภทการอ้างอิง" โดยพื้นฐานแล้วจะอธิบายถึงสิ่งที่อยู่ในหน่วยความจำที่ใช้โดยตัวแปรไม่ใช่กฎที่ยากและรวดเร็วในการที่วัตถุอาศัยอยู่
cHao

21

เพื่อทำความเข้าใจว่าเกิดอะไรขึ้นนี่คือข้อเท็จจริงบางประการ:

  • วัตถุจะถูกจัดสรรบนฮีปเสมอ
  • ฮีปมีเฉพาะวัตถุ
  • ชนิดของค่าถูกจัดสรรบนสแต็กหรือบางส่วนของอ็อบเจ็กต์บนฮีป
  • อาร์เรย์คือวัตถุ
  • อาร์เรย์สามารถมีได้เฉพาะประเภทค่าเท่านั้น
  • การอ้างอิงวัตถุคือประเภทค่า

ดังนั้นหากคุณมีอาร์เรย์ของจำนวนเต็มอาร์เรย์จะถูกจัดสรรบนฮีปและจำนวนเต็มที่มีเป็นส่วนหนึ่งของอ็อบเจ็กต์อาร์เรย์บนฮีป จำนวนเต็มอยู่ภายในอ็อบเจ็กต์อาร์เรย์บนฮีปไม่ใช่อ็อบเจ็กต์แยกต่างหากดังนั้นจึงไม่อยู่ในกล่อง

หากคุณมีอาร์เรย์ของสตริงนั่นคืออาร์เรย์ของการอ้างอิงสตริง เนื่องจากการอ้างอิงเป็นประเภทค่าจึงจะเป็นส่วนหนึ่งของออบเจ็กต์อาร์เรย์บนฮีป ถ้าคุณใส่สตริงอ็อบเจกต์ในอาร์เรย์จริงๆแล้วคุณใส่การอ้างอิงไปยังอ็อบเจ็กต์สตริงในอาร์เรย์และสตริงเป็นอ็อบเจ็กต์แยกต่างหากบนฮีป


ใช่การอ้างอิงทำงานเหมือนกับประเภทค่า แต่ฉันสังเกตว่ามักจะไม่เรียกแบบนั้นหรือรวมอยู่ในประเภทค่า ดูตัวอย่างเช่น (แต่มีมากกว่านี้อีกมากมาย) msdn.microsoft.com/en-us/library/s1ax56ch.aspx
Henk Holterman

@Henk: ใช่คุณคิดถูกแล้วที่การอ้างอิงไม่ได้อยู่ในรายการตัวแปรประเภทค่า แต่เมื่อพูดถึงวิธีการจัดสรรหน่วยความจำสำหรับพวกเขาพวกเขาจะอยู่ในประเภทค่าทุกประการและเป็นประโยชน์อย่างยิ่งที่จะต้องเข้าใจว่าการจัดสรรหน่วยความจำ ทั้งหมดเข้าด้วยกัน :)
Guffa

ฉันสงสัยข้อที่ 5 "อาร์เรย์สามารถมีได้เฉพาะประเภทค่าเท่านั้น" แล้วสตริงอาร์เรย์ล่ะ? สตริง [] สตริง = สตริงใหม่ [4];
Sunil Purushothaman

9

ฉันคิดว่าหัวใจหลักของคำถามของคุณคือความเข้าใจผิดเกี่ยวกับการอ้างอิงและประเภทค่า นี่เป็นสิ่งที่นักพัฒนา. NET และ Java ทุกคนต้องดิ้นรน

อาร์เรย์เป็นเพียงรายการของค่า ถ้าเป็นอาร์เรย์ของประเภทการอ้างอิง (พูดว่า a string[]) อาร์เรย์คือรายการการอ้างอิงไปยังstringวัตถุต่างๆบนฮีปเนื่องจากการอ้างอิงคือค่าของประเภทการอ้างอิง ภายในการอ้างอิงเหล่านี้ถูกนำไปใช้เป็นตัวชี้ไปยังที่อยู่ในหน่วยความจำ หากคุณต้องการให้เห็นภาพนี้อาร์เรย์ดังกล่าวจะมีลักษณะเช่นนี้ในหน่วยความจำ (บนฮีป):

[ 00000000, 00000000, 00000000, F8AB56AA ]

นี่คืออาร์เรย์stringที่มีการอ้างอิงถึงstringอ็อบเจ็กต์4 รายการบนฮีป (ตัวเลขในที่นี้เป็นเลขฐานสิบหก) ปัจจุบันมีเพียงจุดสุดท้ายเท่านั้นที่stringชี้ไปที่อะไรก็ได้ (หน่วยความจำเริ่มต้นเป็นศูนย์ทั้งหมดเมื่อจัดสรร) อาร์เรย์นี้จะเป็นผลลัพธ์ของรหัสนี้ใน C #

string[] strings = new string[4];
strings[3] = "something"; // the string was allocated at 0xF8AB56AA by the CLR

อาร์เรย์ด้านบนจะอยู่ในโปรแกรม 32 บิต ในโปรแกรม 64 บิตการอ้างอิงจะใหญ่เป็นสองเท่า ( F8AB56AAจะเป็น00000000F8AB56AA)

หากคุณมีอาร์เรย์ประเภทค่า (พูดว่า an int[]) อาร์เรย์จะเป็นรายการของจำนวนเต็มเนื่องจากค่าของประเภทค่าคือค่าของตัวมันเอง (ดังนั้นชื่อ) การแสดงภาพของอาร์เรย์ดังกล่าวจะเป็นดังนี้:

[ 00000000, 45FF32BB, 00000000, 00000000 ]

นี่คืออาร์เรย์ของจำนวนเต็ม 4 จำนวนโดยเฉพาะ int ที่สองเท่านั้นที่ได้รับการกำหนดค่า (เป็น 1174352571 ซึ่งเป็นการแทนค่าทศนิยมของเลขฐานสิบหกนั้น) และส่วนที่เหลือของจำนวนเต็มจะเป็น 0 (อย่างที่บอกหน่วยความจำเริ่มต้นเป็นศูนย์ และ 00000000 ในเลขฐานสิบหกคือ 0 ในฐานสิบ) รหัสที่สร้างอาร์เรย์นี้จะเป็น:

 int[] integers = new int[4];
 integers[1] = 1174352571; // integers[1] = 0x45FF32BB would be valid too

int[]อาร์เรย์นี้จะถูกเก็บไว้ในฮีปด้วย

อีกตัวอย่างหนึ่งหน่วยความจำของshort[4]อาร์เรย์จะมีลักษณะดังนี้:

[ 0000, 0000, 0000, 0000 ]

เนื่องจากค่าของ a shortเป็นตัวเลข 2 ไบต์

ในกรณีที่ประเภทค่าถูกจัดเก็บเป็นเพียงรายละเอียดการนำไปใช้งานตามที่ Eric Lippert อธิบายไว้เป็นอย่างดีที่นี่ไม่ใช่ความแตกต่างระหว่างประเภทค่าและประเภทอ้างอิง (ซึ่งเป็นความแตกต่างในพฤติกรรม)

เมื่อคุณส่งบางอย่างไปยังเมธอด (ไม่ว่าจะเป็นประเภทการอ้างอิงหรือประเภทค่า) สำเนาของค่าของประเภทจะถูกส่งไปยังเมธอด ในกรณีของประเภทการอ้างอิงค่าจะเป็นการอ้างอิง (คิดว่าสิ่งนี้เป็นตัวชี้ไปยังหน่วยความจำแม้ว่านั่นจะเป็นรายละเอียดการนำไปใช้งานด้วยก็ตาม) และในกรณีของประเภทค่าค่าก็คือสิ่งนั้นเอง

// Calling this method creates a copy of the *reference* to the string
// and a copy of the int itself, so copies of the *values*
void SomeMethod(string s, int i){}

การชกมวยเกิดขึ้นก็ต่อเมื่อคุณแปลงประเภทค่าเป็นประเภทอ้างอิง รหัสนี้กล่อง:

object o = 5;

ฉันเชื่อว่า "รายละเอียดการใช้งาน" ควรเป็นขนาดตัวอักษร: 50px ;)
sisve

3

นี่คือภาพประกอบที่แสดงคำตอบข้างต้นโดย @P Daddy

ป้อนคำอธิบายภาพที่นี่

ป้อนคำอธิบายภาพที่นี่

และฉันก็แสดงเนื้อหาที่สอดคล้องกันในสไตล์ของฉัน

ป้อนคำอธิบายภาพที่นี่


@P พ่อฉันทำภาพประกอบ โปรดตรวจสอบว่ามีส่วนผิดหรือไม่ และฉันมีคำถามเพิ่มเติม 1. เมื่อฉันสร้างอาร์เรย์ประเภท int ความยาว 4 ความยาวข้อมูลความยาว (4) จะถูกเก็บไว้ในหน่วยความจำเสมอ?
YoungMin Park

2. ในภาพประกอบที่สองที่อยู่อาร์เรย์ที่คัดลอกจะถูกเก็บไว้ที่ใด เป็นพื้นที่สแต็กเดียวกันกับที่จัดเก็บที่อยู่ภายในหรือไม่ เป็นกองอื่น แต่เป็นกองเดียวกันหรือไม่? สแต็คมันคนละแบบหรือเปล่า? 3. 32 บิตต่ำ / 32 บิตสูงหมายถึงอะไร? 4. ค่าตอบแทนเป็นเท่าใดเมื่อฉันจัดสรรประเภทค่า (ในตัวอย่างนี้คือโครงสร้าง) บนสแต็กโดยใช้คีย์เวิร์ดใหม่ เป็นที่อยู่ด้วยหรือเปล่า เมื่อฉันตรวจสอบโดยคำสั่งนี้ Console.WriteLine (valType) มันจะแสดงชื่อที่มีคุณสมบัติครบถ้วนเช่น object เช่น ConsoleApp.ValType
YoungMin Park

5. valType.I = 200; คำสั่งนี้หมายความว่าฉันได้รับที่อยู่ของ valType หรือไม่โดยที่อยู่นี้ฉันเข้าถึง I และที่นั่นฉันเก็บ 200 แต่ "บนสแต็ก"
YoungMin Park

2

ทุกคนได้รับการกล่าวถึงเพียงพอแล้ว แต่ถ้าใครบางคนกำลังมองหาตัวอย่างและเอกสารที่ชัดเจน (แต่ไม่เป็นทางการ) เกี่ยวกับฮีปสแต็กตัวแปรโลคัลและตัวแปรแบบคงที่ให้อ้างอิงบทความของ Jon Skeet ที่สมบูรณ์เกี่ยวกับMemory ใน. NET - จะเกิดอะไรขึ้น ที่ไหน

ข้อความที่ตัดตอนมา:

  1. ตัวแปรท้องถิ่นแต่ละตัว (เช่นหนึ่งที่ประกาศในวิธีการ) จะถูกเก็บไว้ในสแตก ซึ่งรวมถึงตัวแปรประเภทการอ้างอิง - ตัวตัวแปรเองอยู่บนสแต็ก แต่อย่าลืมว่าค่าของตัวแปรชนิดการอ้างอิงเป็นเพียงการอ้างอิง (หรือค่าว่าง) ไม่ใช่ตัววัตถุเอง พารามิเตอร์เมธอดจะนับเป็นตัวแปรโลคัลด้วยเช่นกัน แต่หากประกาศด้วยตัวปรับแต่งการอ้างอิงพารามิเตอร์เหล่านี้จะไม่ได้รับสล็อตของตัวเอง แต่แชร์สล็อตกับตัวแปรที่ใช้ในโค้ดการเรียก ดูบทความของฉันเกี่ยวกับการส่งผ่านพารามิเตอร์สำหรับรายละเอียดเพิ่มเติม

  2. ตัวแปรอินสแตนซ์สำหรับชนิดการอ้างอิงจะอยู่บนฮีปเสมอ นั่นคือที่ที่วัตถุนั้น "มีชีวิต"

  3. ตัวแปรอินสแตนซ์สำหรับชนิดค่าจะถูกเก็บไว้ในบริบทเดียวกับตัวแปรที่ประกาศประเภทค่า สล็อตหน่วยความจำสำหรับอินสแตนซ์มีสล็อตสำหรับแต่ละฟิลด์ภายในอินสแตนซ์อย่างมีประสิทธิภาพ นั่นหมายความว่า (ให้คะแนนสองจุดก่อนหน้านี้) ว่าตัวแปรโครงสร้างที่ประกาศภายในเมธอดจะอยู่บนสแต็กเสมอในขณะที่ตัวแปรโครงสร้างซึ่งเป็นฟิลด์อินสแตนซ์ของคลาสจะอยู่บนฮีป

  4. ตัวแปรคงที่ทุกตัวแปรจะถูกเก็บไว้ในฮีปไม่ว่าจะประกาศภายในประเภทการอ้างอิงหรือประเภทค่าก็ตาม มีเพียงช่องเดียวเท่านั้นไม่ว่าจะสร้างกี่อินสแตนซ์ก็ตาม (ไม่จำเป็นต้องมีอินสแตนซ์ใด ๆ ที่สร้างขึ้นเพื่อให้สล็อตนั้นมีอยู่) รายละเอียดของฮีปที่ตัวแปรอาศัยอยู่นั้นซับซ้อน แต่อธิบายโดยละเอียดในบทความ MSDN ในหัวข้อนี้


ลิงก์ "ไปไหน" ของคุณตายแล้ว
Jabba

ฉันไม่สามารถแก้ไข atm ได้ลิงก์ที่ถูกต้องไปยังบทความของ Skeet คือ: jonskeet.uk/csharp/memory.html
Kale_Surfer_Dude

1

อาร์เรย์ของจำนวนเต็มถูกจัดสรรบนฮีปไม่มีอะไรมากหรือน้อย myIntegers อ้างอิงถึงจุดเริ่มต้นของส่วนที่มีการจัดสรร ints การอ้างอิงนั้นตั้งอยู่บนสแตก

หากคุณมีอาร์เรย์ของอ็อบเจ็กต์ประเภทการอ้างอิงเช่น Object type, myObjects [] ซึ่งอยู่บนสแต็กจะอ้างอิงถึงกลุ่มของค่าที่อ้างอิงอ็อบเจ็กต์ที่เป็นตัวของตัวเอง

สรุปได้ว่าถ้าคุณส่ง myIntegers ไปยังฟังก์ชันบางอย่างคุณจะส่งต่อการอ้างอิงไปยังตำแหน่งที่จัดสรรจำนวนเต็มจริงเท่านั้น


1

ไม่มีการชกมวยในโค้ดตัวอย่างของคุณ

ประเภทค่าสามารถอยู่บนฮีปได้เช่นเดียวกับในอาร์เรย์ของ ints ของคุณ อาร์เรย์ได้รับการจัดสรรบนฮีปและจัดเก็บ ints ซึ่งเป็นชนิดค่า เนื้อหาของอาร์เรย์เริ่มต้นเป็นค่าเริ่มต้น (int) ซึ่งจะเป็นศูนย์

พิจารณาคลาสที่มีประเภทค่า:


    class HasAnInt
    {
        int i;
    }

    HasAnInt h = new HasAnInt();

ตัวแปร h หมายถึงอินสแตนซ์ของ HasAnInt ที่อาศัยอยู่บนฮีป มันมีประเภทค่าอยู่ ไม่เป็นไร 'ฉัน' เพิ่งจะอาศัยอยู่บนกองที่มีอยู่ในชั้นเรียน ไม่มีมวยในตัวอย่างนี้เช่นกัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.