ทำไมหนังสือ. Net พูดถึงการจัดสรรหน่วยความจำ stack vs heap


36

ดูเหมือนว่าหนังสือ. net ทุกเล่มจะพูดถึงประเภทค่าเทียบกับประเภทการอ้างอิงและทำให้เป็นจุด (มักจะไม่ถูกต้อง) ซึ่งเป็นที่เก็บแต่ละประเภท - กองหรือกองซ้อน โดยปกติจะอยู่ในบทแรก ๆ และนำเสนอเป็นข้อเท็จจริงที่สำคัญบางอย่าง ฉันคิดว่ามันครอบคลุมแม้ในการสอบรับรอง ทำไม stack vs heap ถึงสำคัญสำหรับนักพัฒนา. net คุณจัดสรรสิ่งของและใช้งานได้ใช่ไหม


11
ผู้เขียนบางคนมีวิจารณญาณที่ไม่ดีจริงๆในสิ่งที่สำคัญในการสอนผู้เริ่มต้นและสิ่งที่เป็นเสียงที่ไม่เกี่ยวข้อง ในหนังสือที่ฉันเห็นเมื่อเร็ว ๆ นี้การกล่าวถึงครั้งแรกของตัวดัดแปลงการเข้าถึงรวมถึงการป้องกันภายในแล้วซึ่งฉันไม่เคยใช้ใน 6 ปีของ C # ...
Timwi

1
ฉันเดาว่าใครก็ตามที่เขียนเอกสาร. Net ดั้งเดิมสำหรับส่วนนั้นทำเรื่องใหญ่ ๆ และเอกสารนั้นเป็นสิ่งที่ผู้เขียนเคยยึดตามหนังสือมาก่อนแล้วมันก็อยู่เฉยๆ
เกร็ก

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

สัมภาษณ์ลัทธิคาร์โก้?
Den

คำตอบ:


37

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

ดังที่เฟดชี้ให้เห็น Eric Lippert มีบางสิ่งที่น่าสนใจมาก ๆ ที่จะพูดเกี่ยวกับสิ่งนี้: http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx .

ในแง่ของข้อมูลคุณสามารถปรับย่อหน้าแรกของฉันให้อ่านโดยทั่วไป: "เหตุผลที่ผู้คนใส่ข้อมูลนี้และคิดว่ามันสำคัญเพราะข้อมูลที่ไม่ถูกต้องหรือไม่ครบถ้วนรวมกับความต้องการความรู้นี้ในอดีต"

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


6
ฉันได้ยินมาว่าในการใช้งานเฟรมเวิร์ก (ขนาดกะทัดรัดบน Xbox โดยเฉพาะ) จะเป็นการดีกว่าที่จะใช้ structs ในช่วงระยะเวลาเรนเดอร์ (ตัวเกม) เพื่อลดการเก็บขยะ คุณยังคงใช้ประเภทปกติที่อื่น แต่ได้รับการจัดสรรล่วงหน้าดังนั้น GC จะไม่ทำงานในระหว่างเกม นั่นเป็นเรื่องของการเพิ่มประสิทธิภาพเพียงอย่างเดียวเกี่ยวกับ stack vs heap ที่ฉันรู้จักใน. NET และมันค่อนข้างเฉพาะเจาะจงกับความต้องการของคอมแพคเฟรมและโปรแกรมเรียลไทม์
CodexArcanum

5
ฉันเห็นด้วยกับข้อโต้แย้งของประเพณีเป็นส่วนใหญ่ โปรแกรมเมอร์ที่มีประสบการณ์หลายคนในบางจุดอาจมีโปรแกรมในภาษาระดับต่ำซึ่งสิ่งนี้สำคัญถ้าคุณต้องการรหัสที่ถูกต้องและมีประสิทธิภาพ อย่างไรก็ตามยกตัวอย่าง C ++, ภาษาที่ไม่มีการจัดการ: สเปคอย่างเป็นทางการไม่ได้บอกว่าตัวแปรอัตโนมัติต้องอยู่บนสแต็กเป็นต้นมาตรฐานของ C ++ จะใช้กับสแต็กและฮีปเป็นรายละเอียดการใช้งาน +1
stakx

36

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

ฉันเห็นด้วยอย่างสมบูรณ์ ฉันเห็นสิ่งนี้ตลอดเวลา

เหตุใดหนังสือ. NET จึงพูดถึงการจัดสรรหน่วยความจำแบบกองซ้อนกับกองซ้อน?

สาเหตุส่วนหนึ่งเป็นเพราะคนจำนวนมากมาที่ C # (หรือภาษา. NET อื่น ๆ ) จากพื้นหลัง C หรือ C ++ เนื่องจากภาษาเหล่านั้นไม่ได้บังคับใช้กฎของคุณเกี่ยวกับอายุการเก็บข้อมูลคุณจึงจำเป็นต้องรู้กฎเหล่านั้นและใช้โปรแกรมของคุณอย่างระมัดระวังเพื่อติดตาม

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

เมื่อเขียนหนังสือมือใหม่มันเป็นเรื่องธรรมดาที่ผู้เขียนจะอธิบายแนวคิดในลำดับเดียวกันกับที่พวกเขาเรียนรู้ นั่นไม่จำเป็นต้องเป็นคำสั่งที่สมเหตุสมผลสำหรับผู้ใช้ เมื่อไม่นานมานี้ฉันเป็นบรรณาธิการด้านเทคนิคสำหรับหนังสือผู้เริ่มต้น C # 4 ของ Scott Dorman และหนึ่งในสิ่งที่ฉันชอบก็คือ Scott เลือกเรียงลำดับหัวข้อที่ค่อนข้างสมเหตุสมผลแทนที่จะเริ่มหัวข้อการจัดการหน่วยความจำขั้นสูง

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

ทำไม stack vs heap ถึงสำคัญสำหรับนักพัฒนา. NET

ในความคิดของฉันมันไม่ได้ สิ่งที่สำคัญกว่าที่จะเข้าใจก็คือ:

  • ความแตกต่างในความหมายของการคัดลอกระหว่างประเภทอ้างอิงและประเภทค่าคืออะไร?
  • พารามิเตอร์ "ref int x" ทำงานอย่างไร
  • เหตุใดค่าประเภทจึงไม่เปลี่ยนรูป

และอื่น ๆ

คุณจัดสรรสิ่งของและใช้งานได้ใช่ไหม

นั่นคืออุดมคติ

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

ในทำนองเดียวกันมีสถานการณ์จำลองที่เหมือนจริงซึ่งจำเป็นต้องรู้ว่าอะไรอยู่ในสแต็กและอะไรที่อยู่ในกองและสิ่งที่ตัวเก็บขยะสามารถเคลื่อนที่ไปมาได้ นั่นเป็นสาเหตุที่ C # มีคุณสมบัติเช่น "แก้ไข", "สแต็กออลล์" และอื่น ๆ

แต่นั่นเป็นสถานการณ์ขั้นสูงทั้งหมด โปรแกรมเมอร์ที่เริ่มต้นนั้นไม่จำเป็นต้องกังวลเกี่ยวกับสิ่งนี้เลย


2
ขอบคุณสำหรับคำตอบของ Eric โพสต์บล็อกล่าสุดของคุณในหัวข้อเป็นสิ่งที่กระตุ้นให้ฉันโพสต์คำถาม
เกร็ก

13

พวกคุณทุกคนหายจุด เหตุผลที่แตกต่างแต็ค / กองเป็นสิ่งสำคัญเป็นเพราะขอบเขต

struct S { ... }

void f() {
    var x = new S();
    ...
 }

เมื่อxออกไปจากขอบเขตวัตถุที่ถูกสร้างขึ้นเป็นเด็ดขาดไปแล้ว นั่นเป็นเพียงเพราะมันถูกจัดสรรบนสแต็กไม่ใช่กอง ไม่มีอะไรที่จะเข้าไปในส่วนของ "... " ของวิธีการที่สามารถเปลี่ยนความจริงนั้นได้ โดยเฉพาะอย่างยิ่งการมอบหมายหรือการเรียกใช้เมธอดสามารถทำสำเนาของโครงสร้าง S เท่านั้นไม่ได้สร้างการอ้างอิงใหม่เพื่อให้สามารถใช้งานได้

class C { ... }

void f() {
     var x = new C();
     ...
}

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

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


ฉันไม่แน่ใจว่าฉันเคยเห็นข้อโต้แย้งที่นำเสนอก่อนหน้านี้ในหนังสือพร้อมกับการอภิปรายสแต็ค / กอง +1
Greg

2
เนื่องจากวิธีที่ C # สร้างการปิดรหัสใน...อาจทำให้เกิดxการแปลงเป็นเขตข้อมูลของคลาสที่คอมไพเลอร์สร้างขึ้นและอยู่ได้นานกว่าขอบเขตที่ระบุ โดยส่วนตัวแล้วฉันพบว่าความคิดที่น่ารังเกียจในการชักรอกโดยปริยาย แต่นักออกแบบภาษาดูเหมือนจะเป็นเช่นนั้น (ตรงข้ามกับการกำหนดให้ตัวแปรใด ๆ เพื่อให้แน่ใจว่าโปรแกรมนั้นถูกต้องบ่อยครั้งที่ต้องคำนึงถึงการอ้างอิงทั้งหมดที่อาจมีอยู่ในวัตถุ การรู้ว่าเมื่อถึงเวลาที่กิจวัตรกลับคืนจะไม่มีการคัดลอกข้อมูลอ้างอิงที่ส่งผ่านมาอีกต่อไปจะมีประโยชน์
supercat

1
สำหรับ 'structs ที่อยู่ในสแต็ค' คำสั่งที่เหมาะสมคือถ้าประกาศstructType fooที่เก็บข้อมูลเป็นที่เก็บสินค้าเก็บfooเนื้อหาของเขตข้อมูลนั้น ถ้าfooอยู่บนสแต็กดังนั้นฟิลด์ของมัน ถ้าfooอยู่บนกองมันทุ่งก็เช่นกัน ถ้าfooอยู่ในเครือข่าย Apple II ดังนั้นฟิลด์ของมัน ในทางตรงกันข้ามถ้าfooเป็นประเภทคลาสก็จะถืออย่างใดอย่างหนึ่งnullหรืออ้างอิงถึงวัตถุ สถานการณ์เดียวในประเภทคลาสที่fooสามารถกล่าวว่าเก็บเขตข้อมูลของวัตถุจะเป็นถ้ามันเป็นเขตข้อมูลเดียวของชั้นเรียนและถือการอ้างอิงถึงตัวเอง
supercat

+1 ฉันรักความเข้าใจที่นี่และฉันคิดว่ามันถูกต้อง ... อย่างไรก็ตามฉันไม่รู้สึกว่ามันเป็นเหตุผลว่าทำไมหนังสือจึงครอบคลุมหัวข้อนี้ในเชิงลึก ดูเหมือนว่าสิ่งที่คุณอธิบายที่นี่สามารถแทนที่หนังสือ 3 หรือ 4 บทที่กล่าวมาเหล่านั้นและเป็นวิธีที่เป็นประโยชน์มากขึ้น
Frank V

1
จากสิ่งที่ฉันรู้แล้ว structs ไม่จำเป็นต้องทำหรือไม่ก็จะอยู่ในกองซ้อนเสมอ
ร่า

5

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

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


2
การโพสต์ของ Eric ทำให้ประเด็นที่คุณต้องรู้ก็คือคุณลักษณะที่เปิดเผยของประเภทค่าและการอ้างอิงและไม่ควรคาดหวังว่าการนำไปปฏิบัติจะยังคงเหมือนเดิม ฉันคิดว่ามันเป็นคำถามขอทานที่จะแนะนำว่ามีวิธีที่มีประสิทธิภาพในการใช้ C # อีกครั้งโดยไม่ต้องใช้ stack แต่ประเด็นของเขาถูกต้อง: ไม่ใช่ส่วนหนึ่งของข้อมูลจำเพาะภาษา ดังนั้นเหตุผลเดียวที่จะใช้คำอธิบายนี้ที่ฉันคิดได้ก็คือมันเป็นสัญลักษณ์เปรียบเทียบที่มีประโยชน์สำหรับโปรแกรมเมอร์ที่รู้จักภาษาอื่นโดยเฉพาะ C. ตราบใดที่พวกเขารู้ว่าเป็นการเปรียบเทียบซึ่งวรรณกรรมส่วนใหญ่ไม่ชัดเจน
Jeremy

5

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

ฉันไม่รู้เกี่ยวกับ. NET แต่เท่าที่ฉันรู้ JITted มันก่อนที่จะดำเนินการ ตัวอย่างเช่น JIT สามารถทำการวิเคราะห์การหลบหนีและสิ่งที่ไม่และในทันใดคุณจะต้องมีวัตถุวางอยู่บนกองซ้อนหรือเพียงแค่ในการลงทะเบียนบางอย่าง คุณไม่สามารถรู้สิ่งนี้

ฉันคิดว่าหนังสือบางเล่มครอบคลุมเพียงเพราะผู้แต่งให้ความสำคัญกับมันมากหรือเพราะพวกเขาคิดว่าผู้ชมของพวกเขาทำ (เช่นถ้าคุณเขียน "C # สำหรับโปรแกรมเมอร์ C ++" คุณน่าจะครอบคลุมหัวข้อ)

อย่างไรก็ตามฉันคิดว่าไม่มีอะไรจะพูดมากไปกว่า "การจัดการหน่วยความจำ" มิฉะนั้นผู้คนอาจวาดข้อสรุปที่ผิด


2

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


2
ในภาษาที่มีการจัดการคุณต้องทราบความแตกต่างระหว่างประเภทค่าและประเภทอ้างอิง แต่ยิ่งไปกว่านั้นมันง่ายที่จะห่อหุ้มเพลาโดยคำนึงถึงวิธีการจัดการภายใต้ประทุน ดูที่นี่สำหรับตัวอย่าง: stackoverflow.com/questions/4083981/…
Robert Harvey

ฉันต้องเห็นด้วยกับโรเบิร์ต

ความแตกต่างระหว่างการจัดสรรฮีปและการจัดสรรสแต็กเป็นสิ่งที่อธิบายความแตกต่างระหว่างค่าและประเภทการอ้างอิง
Jeremy


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

2

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

อย่างไรก็ตามส่วนใหญ่มันเป็นวิชาการที่สวย


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

0

อย่างที่คุณพูด C # ควรที่จะแยกการจัดการหน่วยความจำออกจากกันและการจัดสรรฮีปกับสแต็กเป็นรายละเอียดการใช้งานซึ่งตามทฤษฎีแล้วผู้พัฒนาไม่จำเป็นต้องรู้

ปัญหาคือบางสิ่งที่ยากมากที่จะอธิบายด้วยวิธีที่เข้าใจง่ายโดยไม่ต้องอ้างถึงรายละเอียดการใช้งานเหล่านี้ พยายามอธิบายพฤติกรรมที่สังเกตได้เมื่อคุณแก้ไขประเภทค่าที่ไม่แน่นอนซึ่งแทบเป็นไปไม่ได้เลยที่จะทำโดยไม่อ้างอิงถึงความแตกต่างของกองซ้อน / กอง หรือพยายามอธิบายว่าทำไมถึงมีประเภทค่าในภาษาในตอนแรกและเมื่อใดที่คุณจะใช้? คุณต้องเข้าใจความแตกต่างเพื่อให้เข้าใจภาษา

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

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