กองและกองคืออะไรและอยู่ที่ไหน


8100

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

  • พวกเขาอยู่ที่ไหนและอะไร (ทางกายภาพในหน่วยความจำของคอมพิวเตอร์จริง)?
  • มีการควบคุมโดยระบบปฏิบัติการหรือภาษาที่ใช้งานในระดับเท่าใด
  • ขอบเขตของพวกเขาคืออะไร?
  • อะไรเป็นตัวกำหนดขนาดของแต่ละคน
  • อะไรทำให้เร็วขึ้น

175
คำอธิบายที่ดีจริง ๆ สามารถพบได้ที่นี่ความแตกต่างระหว่างสแต็คและกองคืออะไร?
Songo

12
นอกจากนี้ (จริง ๆ ) ดี: codeproject.com/Articles/76153/… (ส่วนสแต็ค / กอง)
Ben


3
ที่เกี่ยวข้องดูกองการปะทะกัน สแต็ค remediations rlimit_stackการปะทะกันได้รับผลกระทบบางแง่มุมของตัวแปรระบบและพฤติกรรมเช่น ดูที่ Red Hat Issue 1463241
jww

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

คำตอบ:


5964

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

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

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

หากต้องการตอบคำถามของคุณโดยตรง:

มีการควบคุมโดย OS หรือรันไทม์ภาษาในระดับใด

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

ขอบเขตของพวกเขาคืออะไร?

สแต็กถูกแนบกับเธรดดังนั้นเมื่อเธรดออกจากสแต็กจะถูกเรียกคืน โดยทั่วไปฮีปจะถูกจัดสรรเมื่อเริ่มต้นแอปพลิเคชันโดยรันไทม์และจะถูกเรียกคืนเมื่อแอปพลิเคชัน

อะไรเป็นตัวกำหนดขนาดของแต่ละคน

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

อะไรทำให้เร็วขึ้น

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

การสาธิตที่ชัดเจน:
แหล่งรูปภาพ: vikashazrati.wordpress.com


74
คำตอบที่ดี - แต่ฉันคิดว่าคุณควรเพิ่มในขณะที่สแต็คถูกจัดสรรโดยระบบปฏิบัติการเมื่อกระบวนการเริ่มต้น (สมมติว่ามีระบบปฏิบัติการอยู่) แต่จะได้รับการบำรุงรักษาแบบอินไลน์โดยโปรแกรม นี่เป็นอีกเหตุผลหนึ่งที่ทำให้สแต็กนั้นเร็วขึ้นเช่นกัน - การดำเนินงานแบบพุชและป๊อปมักเป็นคำสั่งเครื่องหนึ่งและเครื่องจักรที่ทันสมัยสามารถทำได้อย่างน้อย 3 รอบในหนึ่งรอบในขณะที่การจัดสรรหรือปล่อยให้กอง
sqykly

276
ในตอนท้ายฉันสับสนด้วยแผนภาพ ฉันคิดว่าฉันได้รับจนกว่าฉันจะเห็นภาพนั้น
Sina Madani

10
@Anarelle ตัวประมวลผลรันคำสั่งโดยมีหรือไม่มีระบบปฏิบัติการ ตัวอย่างที่ใกล้กับใจของฉันคือ SNES ซึ่งไม่มีการเรียก API ไม่มีระบบปฏิบัติการที่เรารู้จักในวันนี้ - แต่มันมีสแต็ก การจัดสรรสแต็กคือการบวกและการลบในระบบเหล่านี้และเป็นสิ่งที่ดีสำหรับตัวแปรที่ถูกทำลายเมื่อพวกมันถูกผุดขึ้นโดยกลับมาจากฟังก์ชั่นที่สร้างพวกเขาขึ้นมา โยนทิ้งไป. สำหรับสิ่งที่เราต้องการกองซึ่งไม่ได้ผูกติดอยู่กับการโทรและกลับ ระบบปฏิบัติการส่วนใหญ่มี APIs จำนวนมากโดยไม่มีเหตุผลที่จะทำด้วยตัวคุณเอง
sqykly

2
"สแต็คคือหน่วยความจำที่ตั้งไว้เป็นพื้นที่สำรอง" เย็น. แต่ที่จริงแล้วมัน "ตั้งสำรอง" ในแง่ของโครงสร้างหน่วยความจำ Java หรือไม่? Is Heap memory / หน่วยความจำ Non-heap / อื่น ๆ (โครงสร้างหน่วยความจำ Java ตาม betsol.com/2017/06/ … )
Jatin Shashoo

4
@JatinShashoo Java runtime เป็นล่าม bytecode เพิ่ม virtualization อีกระดับหนึ่งดังนั้นสิ่งที่คุณอ้างถึงเป็นเพียงมุมมองของแอปพลิเคชัน Java จากมุมมองระบบปฏิบัติการทั้งหมดเป็นเพียงแค่ฮีพที่กระบวนการรันไทม์ Java จัดสรรพื้นที่บางส่วนเป็นหน่วยความจำ "non-heap" สำหรับการประมวลผล bytecode ส่วนที่เหลือของฮีประดับ OS นั้นจะใช้เป็นฮีประดับแอปพลิเคชันซึ่งเก็บข้อมูลของวัตถุ
kbec

2349

ซ้อนกัน:

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

กอง:

  • เก็บไว้ใน RAM คอมพิวเตอร์เช่นเดียวกับสแต็ค
  • ใน C ++ ตัวแปรบนฮีปต้องถูกทำลายด้วยตนเองและไม่หลุดออกจากขอบเขต ข้อมูลจะถูกปล่อยให้เป็นอิสระด้วยdelete, หรือdelete[]free
  • การจัดสรรช้ากว่าเมื่อเปรียบเทียบกับตัวแปรในสแต็ก
  • ใช้ตามความต้องการเพื่อจัดสรรบล็อกข้อมูลสำหรับใช้งานโดยโปรแกรม
  • สามารถมีการแตกแฟรกเมนต์เมื่อมีการจัดสรรและการจัดสรรคืนจำนวนมาก
  • ใน C ++ หรือ C ข้อมูลที่สร้างบนฮีปจะถูกชี้โดยตัวชี้และจัดสรรด้วยnewหรือmallocตามลำดับ
  • สามารถมีความล้มเหลวในการจัดสรรหากมีการร้องขอบัฟเฟอร์ใหญ่เกินไปที่จะจัดสรร
  • คุณจะใช้ฮีปถ้าคุณไม่รู้ว่าคุณต้องใช้ข้อมูลมากแค่ไหนในขณะใช้งานหรือถ้าคุณต้องการจัดสรรข้อมูลจำนวนมาก
  • รับผิดชอบการรั่วไหลของหน่วยความจำ

ตัวอย่าง:

int foo()
{
  char *pBuffer; //<--nothing allocated yet (excluding the pointer itself, which is allocated here on the stack).
  bool b = true; // Allocated on the stack.
  if(b)
  {
    //Create 500 bytes on the stack
    char buffer[500];

    //Create 500 bytes on the heap
    pBuffer = new char[500];

   }//<-- buffer is deallocated here, pBuffer is not
}//<--- oops there's a memory leak, I should have called delete[] pBuffer;

31
ตัวชี้ pBuffer และค่าของ b ตั้งอยู่บนสแต็กและส่วนใหญ่จะถูกจัดสรรที่ทางเข้าสู่ฟังก์ชัน ขึ้นอยู่กับคอมไพเลอร์บัฟเฟอร์อาจถูกจัดสรรที่ทางเข้าฟังก์ชั่นเช่นกัน
Andy

36
เป็นความเข้าใจผิดทั่วไปที่Cภาษาตามที่กำหนดโดยC99มาตรฐานภาษา (มีให้ที่open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf ) ต้องใช้ "stack" ในความเป็นจริงคำว่า 'สแต็ค' ไม่ปรากฏในมาตรฐาน คำตอบนี้เป็นคำชี้แจงCการใช้งานสแต็คของwrt / to เป็นความจริงโดยทั่วไป แต่ไม่จำเป็นต้องใช้ภาษา ดูknosof.co.uk/cbook/cbook.htmlสำหรับข้อมูลเพิ่มเติมและโดยเฉพาะอย่างยิ่งวิธีCการใช้งานในสถาปัตยกรรมคี่บอลเช่นen.wikipedia.org/wiki/Burroughs_large_systems
johne

55
@Brian คุณควรอธิบายว่าทำไมบัฟเฟอร์ [] และตัวชี้ pBuffer ถูกสร้างบนสแต็กและทำไมข้อมูลของ pBuffer จึงถูกสร้างบนฮีป ฉันคิดว่า ppl บางตัวอาจสับสนกับคำตอบของคุณเนื่องจากพวกเขาอาจคิดว่าโปรแกรมกำลังบอกว่าหน่วยความจำจะถูกจัดสรรบน stack vs heap โดยเฉพาะ แต่นี่ไม่ใช่กรณี เป็นเพราะบัฟเฟอร์เป็นประเภทค่าในขณะที่ pBuffer เป็นประเภทอ้างอิง
Howiecamp

9
@Remover: ไม่มีตัวชี้เก็บที่อยู่และสามารถชี้ไปที่บางสิ่งบนฮีปหรือสแต็กอย่างเท่าเทียมกัน ใหม่, malloc และฟังก์ชั่นอื่น ๆ ที่คล้ายกับ malloc จัดสรรบนฮีปและส่งคืนที่อยู่ของหน่วยความจำที่จัดสรรไว้ ทำไมคุณต้องการจัดสรรให้กอง เพื่อให้หน่วยความจำของคุณจะไม่ออกนอกขอบเขตและได้รับการปล่อยตัวจนกว่าคุณต้องการ
Brian R. Bondy

35
"รับผิดชอบการรั่วไหลของหน่วยความจำ" - ฮีปจะไม่รับผิดชอบต่อการรั่วไหลของหน่วยความจำ! Lazy / Forgetful / ex-java coders / coders ที่ไม่มีอึ!
Laz

1370

จุดที่สำคัญที่สุดคือ heap และ stack เป็นคำศัพท์ทั่วไปสำหรับวิธีการจัดสรรหน่วยความจำ สามารถนำไปใช้ได้หลายวิธีและข้อกำหนดนี้ใช้กับแนวคิดพื้นฐาน

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

    สแต็คเหมือนกองเอกสาร

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

  • ในกองไม่มีคำสั่งโดยเฉพาะกับวิธีการวางรายการ คุณสามารถเข้าถึงและลบรายการในลำดับใด ๆ เนื่องจากไม่มีรายการ 'บนสุด' ที่ชัดเจน

    กองเหมือนกองชะเอมทุกชนิด

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

อิมเมจเหล่านี้ควรใช้งานได้ดีในการอธิบายสองวิธีในการจัดสรรและเพิ่มหน่วยความจำในสแต็กและฮีป ยัม!

  • มีการควบคุมโดย OS หรือรันไทม์ภาษาในระดับใด

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

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

  • ขอบเขตของพวกเขาคืออะไร?

    สแตกการโทรเป็นแนวคิดระดับต่ำที่ไม่เกี่ยวข้องกับ 'ขอบเขต' ในแง่ของการเขียนโปรแกรม หากคุณแยกรหัสบางส่วนออกคุณจะเห็นรูปแบบตัวชี้สัมพัทธ์อ้างอิงถึงส่วนต่าง ๆ ของสแต็ก แต่ถ้าเกี่ยวข้องกับภาษาระดับสูงภาษานั้นจะกำหนดขอบเขตของกฎเอง อย่างไรก็ตามสิ่งสำคัญอย่างหนึ่งของสแต็กคือเมื่อฟังก์ชั่นส่งคืนสิ่งที่อยู่ภายในฟังก์ชันนั้นจะถูกปล่อยจากสแต็กทันที วิธีนี้เป็นไปตามที่คุณคาดหวังให้ทำงานได้ตามที่ภาษาโปรแกรมของคุณทำงาน ในกองมันก็ยากที่จะกำหนด ขอบเขตคืออะไรก็ตามที่ OS เปิดเผย แต่ภาษาการเขียนโปรแกรมของคุณอาจเพิ่มกฎเกี่ยวกับสิ่งที่ "ขอบเขต" ในแอปพลิเคชันของคุณ สถาปัตยกรรมโปรเซสเซอร์และระบบปฏิบัติการใช้การกำหนดที่อยู่เสมือน ตัวประมวลผลใดแปลเป็นฟิสิคัลแอดเดรสและมีข้อบกพร่องของเพจ ฯลฯ ซึ่งจะติดตามว่าเพจใดเป็นของแอปพลิเคชันใด คุณไม่จำเป็นต้องกังวลเกี่ยวกับสิ่งนี้จริงๆเพราะคุณเพียงแค่ใช้วิธีการที่ภาษาการเขียนโปรแกรมของคุณใช้เพื่อจัดสรรและเพิ่มหน่วยความจำและตรวจสอบข้อผิดพลาด (หากการจัดสรร / การปลดปล่อยล้มเหลวด้วยเหตุผลใดก็ตาม)

  • อะไรเป็นตัวกำหนดขนาดของแต่ละคน

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

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

  • อะไรทำให้เร็วขึ้น

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


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

8
คำตอบนี้รวมถึงความผิดพลาดครั้งใหญ่ ตัวแปรสแตติกไม่ได้รับการจัดสรรบนสแต็ก ดูคำตอบของฉัน [link] stackoverflow.com/a/13326916/1763801สำหรับการชี้แจง คุณกำลังเท่ากันตัวแปร "อัตโนมัติ" กับตัวแปร "คงที่" แต่พวกเขาจะไม่เหมือนกันทั้งหมด
davec

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

9
ฉันได้ตระหนักถึงเพียงแค่คุณสิทธิกำลัง - ใน C, การจัดสรรคงเป็นสิ่งที่แยกของตัวเองมากกว่าระยะสำหรับสิ่งที่ไม่ได้เป็นแบบไดนามิก ฉันได้แก้ไขคำตอบแล้วขอบคุณ
thomasrutter

5
มันไม่ใช่แค่ซี Java, Pascal, Python และอื่น ๆ อีกมากมายล้วนมีแนวคิดของสเตติกและเปรียบเทียบกับการจัดสรรแบบไดนามิกโดยอัตโนมัติ การพูดว่า "การจัดสรรแบบคงที่" หมายถึงสิ่งเดียวกันทุกที่ การจัดสรรแบบคงที่ไม่มีภาษาหมายความว่า "ไม่ไดนามิก" คุณต้องการการจัดสรรคำว่า "อัตโนมัติ" สำหรับสิ่งที่คุณกำลังอธิบาย (เช่นสิ่งที่อยู่ในสแต็ก)
เดฟ

727

(ฉันได้ย้ายคำตอบนี้จากคำถามอื่นที่มีค่าดักจับของคำถามนี้มากหรือน้อย)

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

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

กอง

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

กอง

สแต็ค

  • สแต็กมักทำงานควบคู่ไปกับการลงทะเบียนพิเศษบน CPU ที่ชื่อตัวชี้สแต็ก เริ่มแรกตัวชี้สแต็กชี้ไปที่ด้านบนของสแต็ก (ที่อยู่สูงสุดของสแต็ก)
  • CPU มีคำแนะนำพิเศษสำหรับการผลักค่าลงบนสแต็กและpoppingกลับจากสแต็ก การพุชแต่ละครั้งจะเก็บค่าที่ตำแหน่งปัจจุบันของตัวชี้สแต็กและลดตัวชี้สแต็ก ป๊อปดึงค่าที่ชี้ไปตามตัวชี้สแต็คแล้วเพิ่มตัวชี้สแต็ค (ไม่ต้องสับสนโดยความจริงที่ว่าการเพิ่มมูลค่าให้กับสแต็คลดลงชี้สแต็คและลบค่าการเพิ่มขึ้นของมัน. จำไว้ว่าสแต็คเติบโต ด้านล่าง). ค่าที่เก็บและดึงมาได้คือค่าของ CPU register
  • เมื่อฟังก์ชั่นถูกเรียกใช้ CPU ใช้คำสั่งพิเศษที่ผลักดันตัวชี้คำสั่งปัจจุบันเช่นที่อยู่ของโค้ดที่เรียกใช้งานบนสแต็ก จากนั้น CPU จะข้ามไปยังฟังก์ชันโดยการตั้งค่าตัวชี้คำสั่งเป็นที่อยู่ของฟังก์ชันที่เรียกว่า ต่อมาเมื่อฟังก์ชั่นกลับมาตัวชี้คำสั่งเก่าจะถูกดึงจากสแต็กและการดำเนินการจะดำเนินการต่อที่รหัสหลังจากการเรียกไปยังฟังก์ชัน
  • เมื่อป้อนฟังก์ชันแล้วตัวชี้สแต็กจะลดลงเพื่อจัดสรรพื้นที่เพิ่มเติมบนสแต็กสำหรับตัวแปรโลคัล (อัตโนมัติ) หากฟังก์ชันมีตัวแปร 32 บิตโลคัลหนึ่งตัวสี่ไบต์จะถูกตั้งค่าไว้บนสแต็ก เมื่อฟังก์ชันส่งคืนตัวชี้สแต็กจะถูกย้ายกลับไปยังพื้นที่ที่จัดสรร
  • หากฟังก์ชั่นมีพารามิเตอร์สิ่งเหล่านี้จะถูกส่งไปยังสแต็กก่อนที่จะเรียกไปยังฟังก์ชั่น รหัสในฟังก์ชั่นนั้นสามารถนำทางขึ้นสแต็คจากตัวชี้สแต็กปัจจุบันเพื่อค้นหาค่าเหล่านี้
  • ฟังก์ชั่นการโทรออกจากรังทำหน้าที่เหมือนจับใจ การเรียกใหม่แต่ละครั้งจะจัดสรรพารามิเตอร์ของฟังก์ชั่นที่อยู่ผู้ส่งและพื้นที่สำหรับตัวแปรท้องถิ่นและเร็กคอร์ดการเปิดใช้งานเหล่านี้สามารถซ้อนกันสำหรับการโทรซ้อนและจะผ่อนคลายในวิธีที่ถูกต้องเมื่อฟังก์ชั่นกลับมา
  • เนื่องจากสแต็กเป็นบล็อกหน่วยความจำที่ จำกัด คุณสามารถทำให้เกิดโอเวอร์โฟลว์สแต็กได้โดยการเรียกใช้ฟังก์ชันซ้อนกันมากเกินไปและ / หรือจัดสรรพื้นที่มากเกินไปสำหรับตัวแปรในเครื่อง บ่อยครั้งที่มีการตั้งค่าพื้นที่หน่วยความจำสำหรับสแต็กในลักษณะที่เขียนด้านล่าง (ที่อยู่ต่ำสุด) ของสแต็กจะทริกเกอร์กับดักหรือข้อยกเว้นใน CPU เงื่อนไขพิเศษนี้สามารถตรวจพบได้โดยรันไทม์และแปลงเป็นข้อยกเว้นสแตกล้นบางชนิด

สแต็ค

สามารถจัดสรรฟังก์ชั่นในฮีปแทนที่จะเป็นสแต็กได้หรือไม่?

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

วิธีจัดการฮีปนั้นขึ้นอยู่กับสภาพแวดล้อมรันไทม์จริงๆ C ใช้mallocและ C ++ ใช้newแต่ภาษาอื่น ๆ อีกมากมายมีการรวบรวมขยะ

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


35
@Martin - คำตอบ / คำอธิบายที่ดีกว่าคำตอบที่เป็นนามธรรม ตัวอย่างแอสเซมบลีโปรแกรมแสดงตัวชี้สแต็ค / การลงทะเบียนที่กำลังใช้การเรียกฟังก์ชัน vis จะเป็นตัวอย่างเพิ่มเติม
Bikal Lem

3
ทุกประเภทการอ้างอิงคือองค์ประกอบของประเภทค่า (int, สตริง ฯลฯ ) ตามที่ได้กล่าวไปแล้วชนิดของค่าจะถูกเก็บไว้ในสแต็กมากกว่าวิธีการทำงานเมื่อเป็นส่วนหนึ่งของประเภทอ้างอิง
Nps

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

3
นี่คือสิ่งที่ดีที่สุดในความคิดของฉันกล่าวคือสำหรับกอง / กองที่เฉพาะเจาะจงมาก คำตอบอื่น ๆ ถือว่ามีหลายอย่างเกี่ยวกับภาษาและสภาพแวดล้อม / ระบบปฏิบัติการ +1
Qix - MONICA ถูกยกเลิก

2
คุณหมายถึงอะไร "รหัสในฟังก์ชั่นนั้นสามารถนำทางขึ้นสแต็คจากตัวชี้สแต็กปัจจุบันเพื่อค้นหาค่าเหล่านี้" ? คุณช่วยอธิบายเรื่องนี้ได้มั้ย
Koray Tugay

404

ในรหัส C # ต่อไปนี้

public void Method1()
{
    int i = 4;
    int y = 2;
    class1 cls1 = new class1();
}

นี่คือวิธีจัดการหน่วยความจำ

รูปภาพของตัวแปรบนสแต็ก

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

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

ใน Java วัตถุส่วนใหญ่จะเข้าสู่กอง ในภาษาเช่น C / C ++, structs และคลาสสามารถอยู่ใน stack ได้บ่อยครั้งเมื่อคุณไม่ได้ติดต่อกับพอยน์เตอร์

ข้อมูลเพิ่มเติมสามารถดูได้ที่นี่:

ความแตกต่างระหว่างการจัดสรรหน่วยความจำสแต็กและฮีป« timmurphy.org

และที่นี่:

การสร้างวัตถุบนกองซ้อนและกอง

บทความนี้เป็นแหล่งที่มาของรูปภาพด้านบน: แนวคิดหกประการที่สำคัญของ. NET: Stack, heap, ประเภทค่า, ประเภทอ้างอิง, Boxing และ unboxing - CodeProject

แต่ระวังว่ามันอาจมีความไม่ถูกต้องอยู่บ้าง


15
สิ่งนี้ไม่ถูกต้อง i และ cls ไม่ใช่ตัวแปร "คงที่" พวกเขาจะเรียกว่าตัวแปร "ท้องถิ่น" หรือ "อัตโนมัติ" มันเป็นความแตกต่างที่สำคัญมาก ดู [link] stackoverflow.com/a/13326916/1763801เพื่อความกระจ่าง
davec

9
ผมไม่ได้บอกว่าพวกเขาเป็นแบบคงที่ตัวแปร ผมบอกว่า int และ cls1 จะคงที่รายการ หน่วยความจำของพวกเขาถูกจัดสรรแบบคงที่และดังนั้นพวกเขาไปในกอง สิ่งนี้ตรงกันข้ามกับวัตถุที่ต้องการการจัดสรรหน่วยความจำแบบไดนามิกซึ่งจะไปยังฮีป
Snowcrash

12
ฉันพูด "รายการคงที่ ... ไปที่สแต็ค" นี่มันผิดไปหมดแล้ว รายการคงที่ไปในส่วนของข้อมูลรายการอัตโนมัติจะไปในกอง
เดฟ

14
นอกจากนี้ใครก็ตามที่เขียนบทความ codeproject ไม่รู้ว่าเขากำลังพูดถึงอะไร ตัวอย่างเช่นเขากล่าวว่า "หน่วยดั้งเดิมต้องการหน่วยความจำประเภทคงที่" ซึ่งไม่จริงอย่างสมบูรณ์ ไม่มีอะไรทำให้คุณไม่สามารถจัดสรรดั้งเดิมในฮีปแบบไดนามิกได้เพียงแค่เขียนบางอย่างเช่น "int array [] = new int [num]" และ voila ซึ่งพื้นฐานถูกจัดสรรแบบไดนามิกใน. NET นั่นเป็นเพียงหนึ่งในความไม่ถูกต้องหลายอย่าง
เดฟ

8
ฉันแก้ไขโพสต์ของคุณเพราะคุณทำผิดพลาดทางเทคนิคอย่างร้ายแรงเกี่ยวกับสิ่งที่เกิดขึ้นในกองซ้อนและกอง
Tom Leys

209

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

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

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

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

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

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

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


16
คำแนะนำเพื่อหลีกเลี่ยงการใช้ฮีปนั้นค่อนข้างแข็งแกร่ง ระบบสมัยใหม่มีผู้จัดการฮีปที่ดีและภาษาไดนามิกที่ทันสมัยใช้ฮีปอย่างกว้างขวาง (โดยไม่ต้องกังวลกับโปรแกรมเมอร์) ฉันว่าจะใช้ฮีป แต่ด้วยตัวจัดสรรคู่มืออย่าลืมให้ฟรี!
Greg Hewgill

2
หากคุณสามารถใช้สแต็กหรือกองให้ใช้กอง หากคุณไม่สามารถใช้สแต็คก็ไม่มีทางเลือกจริงๆ ฉันใช้ทั้งมากและแน่นอนว่าใช้ std :: vector หรือ hit heap ที่คล้ายกัน สำหรับสามเณรคุณหลีกเลี่ยงกองเพราะกองนั้นง่ายมาก !!
Tom Leys

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

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

2
Good point @JonnoHampson - ในขณะที่คุณสร้างจุดที่ถูกต้องฉันจะยืนยันว่าถ้าคุณทำงานใน "ภาษาระดับสูง" กับ GC คุณอาจไม่สนใจกลไกการจัดสรรหน่วยความจำเลย - และอย่าทำเช่นนั้น แม้จะสนใจว่าสแต็คและกองคืออะไร
Tom Leys

194

เพื่อชี้แจงคำตอบนี้มีข้อมูลที่ไม่ถูกต้อง (โทมัสแก้ไขคำตอบของเขาหลังจากความเห็น, เจ๋ง :)) คำตอบอื่น ๆ เพียงหลีกเลี่ยงการอธิบายความหมายของการจัดสรรแบบคงที่ ดังนั้นฉันจะอธิบายสามรูปแบบหลักของการจัดสรรและวิธีที่พวกเขามักจะเกี่ยวข้องกับกองกองและกองข้อมูลด้านล่าง ฉันจะแสดงตัวอย่างทั้งใน C / C ++ และ Python เพื่อช่วยให้ผู้คนเข้าใจ

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

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

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

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

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

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

// Statically allocated in the data segment when the program/DLL is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed from anywhere in the code
int someGlobalVariable;

// Statically allocated in the data segment when the program is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed from anywhere in this particular code file
static int someStaticVariable;

// "someArgument" is allocated on the stack each time MyFunction is called
// "someArgument" is deallocated when MyFunction returns
// scope - can be accessed only within MyFunction()
void MyFunction(int someArgument) {

    // Statically allocated in the data segment when the program is first loaded
    // Deallocated when the program/DLL exits
    // scope - can be accessed only within MyFunction()
    static int someLocalStaticVariable;

    // Allocated on the stack each time MyFunction is called
    // Deallocated when MyFunction returns
    // scope - can be accessed only within MyFunction()
    int someLocalVariable;

    // A *pointer* is allocated on the stack each time MyFunction is called
    // This pointer is deallocated when MyFunction returns
    // scope - the pointer can be accessed only within MyFunction()
    int* someDynamicVariable;

    // This line causes space for an integer to be allocated in the heap
    // when this line is executed. Note this is not at the beginning of
    // the call to MyFunction(), like the automatic variables
    // scope - only code within MyFunction() can access this space
    // *through this particular variable*.
    // However, if you pass the address somewhere else, that code
    // can access it too
    someDynamicVariable = new int;


    // This line deallocates the space for the integer in the heap.
    // If we did not write it, the memory would be "leaked".
    // Note a fundamental difference between the stack and heap
    // the heap must be managed. The stack is managed for us.
    delete someDynamicVariable;

    // In other cases, instead of deallocating this heap space you
    // might store the address somewhere more permanent to use later.
    // Some languages even take care of deallocation for you... but
    // always it needs to be taken care of at runtime by some mechanism.

    // When the function returns, someArgument, someLocalVariable
    // and the pointer someDynamicVariable are deallocated.
    // The space pointed to by someDynamicVariable was already
    // deallocated prior to returning.
    return;
}

// Note that someGlobalVariable, someStaticVariable and
// someLocalStaticVariable continue to exist, and are not
// deallocated until the program exits.

ตัวอย่างที่สำคัญอย่างยิ่งว่าทำไมการแยกแยะความแตกต่างระหว่างอายุการใช้งานและขอบเขตคือตัวแปรสามารถมีขอบเขตภายในเครื่อง แต่อายุการใช้งานแบบคงที่ตัวอย่างเช่น "someLocalStaticVariable" ในตัวอย่างโค้ดด้านบน ตัวแปรดังกล่าวสามารถทำให้พฤติกรรมการตั้งชื่อทั่วไปของเรา แต่ไม่เป็นทางการทำให้เกิดความสับสน ยกตัวอย่างเช่นเมื่อเราพูดว่า " ท้องถิ่น " เรามักจะหมายถึง " ขอบเขตท้องถิ่นตัวแปรที่จัดสรรโดยอัตโนมัติ " และเมื่อเราบอกว่าโลกเรามักจะหมายถึง " ขอบเขตทั่วโลกตัวแปรจัดสรรแบบคงที่ " น่าเสียดายที่เมื่อพูดถึงสิ่งต่างๆเช่น " ไฟล์ที่กำหนดขอบเขตตัวแปรแบบคงที่ " หลายคนแค่พูดว่า ... " huh ??? "

ตัวเลือกไวยากรณ์บางตัวใน C / C ++ ทำให้ปัญหานี้รุนแรงขึ้น - ตัวอย่างเช่นหลายคนคิดว่าตัวแปรทั่วโลกไม่ใช่ "คงที่" เนื่องจากวากยสัมพันธ์แสดงไว้ด้านล่าง

int var1; // Has global scope and static allocation
static int var2; // Has file scope and static allocation

int main() {return 0;}

โปรดทราบว่าการวางคำหลัก "คงที่" ในการประกาศข้างต้นป้องกันไม่ให้ var2 มีขอบเขตทั่วโลก อย่างไรก็ตาม Global var1 มีการจัดสรรแบบคงที่ มันไม่ง่ายเลย! ด้วยเหตุนี้ฉันจึงพยายามไม่ใช้คำว่า "คงที่" เมื่ออธิบายขอบเขตและแทนที่จะพูดถึงบางสิ่งเช่นขอบเขต "ไฟล์" หรือ "จำกัด ไฟล์" อย่างไรก็ตามหลายคนใช้วลี "static" หรือ "static scope" เพื่ออธิบายตัวแปรที่สามารถเข้าถึงได้จากไฟล์รหัสเดียวเท่านั้น ในบริบทของอายุการใช้งาน "สแตติก" เสมอหมายถึงตัวแปรจะถูกจัดสรรเมื่อเริ่มต้นโปรแกรมและยกเลิกการจัดสรรเมื่อโปรแกรมออก

บางคนคิดว่าแนวคิดเหล่านี้เฉพาะ C / C ++ พวกเขาจะไม่. ตัวอย่างเช่น Python ตัวอย่างด้านล่างแสดงการจัดสรรทั้งสามประเภท (มีความแตกต่างเล็กน้อยที่เป็นไปได้ในการตีความภาษาที่ฉันจะไม่เข้าไปที่นี่)

from datetime import datetime

class Animal:
    _FavoriteFood = 'Undefined' # _FavoriteFood is statically allocated

    def PetAnimal(self):
        curTime = datetime.time(datetime.now()) # curTime is automatically allocatedion
        print("Thank you for petting me. But it's " + str(curTime) + ", you should feed me. My favorite food is " + self._FavoriteFood)

class Cat(Animal):
    _FavoriteFood = 'tuna' # Note since we override, Cat class has its own statically allocated _FavoriteFood variable, different from Animal's

class Dog(Animal):
    _FavoriteFood = 'steak' # Likewise, the Dog class gets its own static variable. Important to note - this one static variable is shared among all instances of Dog, hence it is not dynamic!


if __name__ == "__main__":
    whiskers = Cat() # Dynamically allocated
    fido = Dog() # Dynamically allocated
    rinTinTin = Dog() # Dynamically allocated

    whiskers.PetAnimal()
    fido.PetAnimal()
    rinTinTin.PetAnimal()

    Dog._FavoriteFood = 'milkbones'
    whiskers.PetAnimal()
    fido.PetAnimal()
    rinTinTin.PetAnimal()

# Output is:
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is tuna
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is steak
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is steak
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is tuna
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is milkbones
# Thank you for petting me. But it's 13:05:02.256000, you should feed me. My favorite food is milkbones

ฉันจะอ้างถึงตัวแปรแบบคงที่ประกาศภายในฟังก์ชั่นว่ามีเพียงการเข้าถึงท้องถิ่นแต่โดยทั่วไปจะไม่ใช้คำว่า "ขอบเขต" กับมัน ยิ่งไปกว่านั้นมันอาจจะคุ้มค่าที่จะสังเกตว่าสแต็ค / ฮีปเดียวที่ภาษามีความยืดหยุ่นเป็นศูนย์: ภาษาที่บันทึกบริบทการดำเนินการบนสแต็กไม่สามารถใช้สแต็กเดียวกันนั้นเพื่อเก็บสิ่งต่าง ๆ ซึ่งจะต้องมี . บางภาษาเช่นPostScriptมีหลายกอง แต่มี "กอง" ที่ทำงานเหมือนกอง
supercat

@supercat นั่นทำให้รู้สึกทั้งหมด ฉันกำหนดขอบเขตเป็น "ส่วนใดของรหัสที่สามารถเข้าถึงตัวแปร" (และรู้สึกว่านี่เป็นคำจำกัดความมาตรฐานมากที่สุด) ดังนั้นฉันคิดว่าเราเห็นด้วย :)
davec

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

@supercat นี่คือเหตุผลที่ฉันใช้คำว่าอายุการใช้งานซึ่งเป็นวิธีที่ฉันพูดสิ่งที่คุณเรียกขอบเขตเวลา มันลดความจำเป็นในการโอเวอร์โหลดคำว่า "ขอบเขต" ที่มีความหมายมากมาย เท่าที่ฉันสามารถบอกได้ดูเหมือนจะไม่มีฉันทามติทั้งหมดเกี่ยวกับคำจำกัดความที่แน่นอนแม้ว่าแม้ในหมู่แหล่งบัญญัติ คำศัพท์ของฉันถูกดึงมาจาก K&R บางส่วนและบางส่วนจากการใช้ที่แพร่หลายในแผนก CS แรกที่ฉันศึกษา / สอนที่ ดีเสมอที่จะได้ยินมุมมองข้อมูลอื่น
เดฟ

1
คุณต้องล้อเล่น คุณสามารถกำหนดตัวแปรคงที่ภายในฟังก์ชั่นได้หรือไม่?
Zaeem Sattar

168

คนอื่น ๆ ก็ตอบจังหวะในวงกว้างได้ค่อนข้างดีดังนั้นฉันจะเขียนรายละเอียดเล็กน้อย

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

  2. ใน C คุณจะได้รับประโยชน์จากการจัดสรรความยาวผันแปรผ่านการใช้allocaซึ่งจัดสรรบนสแต็กซึ่งต่างจากการจัดสรรซึ่งจัดสรรบนฮีป หน่วยความจำนี้จะไม่รอดจากการคืนค่าของคุณ แต่มีประโยชน์สำหรับการลบบัฟเฟอร์

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

void myfunction()
{
   char big[10000000];
   // Do something that only uses for first 1K of big 99% of the time.
}

เรื่อง "เมื่อเทียบกับการจัดสรร": คุณหมายถึง "เมื่อเทียบกับ malloc" หรือไม่
Peter Mortensen

พกพาได้allocaอย่างไร?
Peter Mortensen

@PeterMortensen ไม่ใช่ POSIX ไม่รับประกันการพกพา
Don Neufeld

135

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

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

ในระบบที่ไม่มีหน่วยความจำเสมือนเช่นระบบฝังตัวบางตัวระบบจะใช้โครงร่างพื้นฐานแบบเดียวกันยกเว้นสแต็กและฮีปจะถูกกำหนดขนาด อย่างไรก็ตามในระบบฝังตัวอื่น ๆ (เช่นที่อยู่บนพื้นฐานของไมโครคอนโทรลเลอร์ PIC ไมโครคอนโทรลเลอร์) สแต็กโปรแกรมเป็นบล็อกหน่วยความจำที่แยกต่างหากซึ่งไม่สามารถระบุตำแหน่งได้โดยคำแนะนำในการเคลื่อนย้ายข้อมูลและสามารถแก้ไขหรืออ่านทางอ้อมผ่าน ผลตอบแทน ฯลฯ ) สถาปัตยกรรมอื่น ๆ เช่นโปรเซสเซอร์ Intel Itanium มีหลายกอง ในแง่นี้สแต็กเป็นองค์ประกอบของสถาปัตยกรรม CPU


117

สแต็คคืออะไร?

สแต็กเป็นกองของวัตถุโดยทั่วไปจะเป็นกองที่จัดเรียงอย่างเรียบร้อย

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

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

กองคืออะไร?

กองคือชุดที่ไม่เป็นระเบียบของสิ่งต่าง ๆ ซ้อนขึ้นอย่างส่งเดช

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

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

ทั้งสองรวมกัน

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

อันไหนเร็วกว่า - กองหรือกอง? และทำไม?

สแต็กนั้นเร็วกว่ากองมาก
นี่เป็นเพราะวิธีการจัดสรรหน่วยความจำบนสแต็ก
การจัดสรรหน่วยความจำบนสแต็กนั้นง่ายเหมือนการเลื่อนตัวชี้สแต็กขึ้น

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

โมเดลหน่วยความจำ Java

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

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


115

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

ฮีปเป็นส่วนหนึ่งของหน่วยความจำที่มอบให้กับแอปพลิเคชันโดยระบบปฏิบัติการโดยทั่วไปผ่าน syscall เช่น malloc ในระบบปฏิบัติการที่ทันสมัยหน่วยความจำนี้เป็นชุดของหน้าเว็บที่กระบวนการเรียกเท่านั้นที่มีสิทธิ์เข้าถึง

ขนาดของสแต็กถูกกำหนดที่รันไทม์และโดยทั่วไปจะไม่ขยายหลังจากโปรแกรมเรียกทำงาน ในโปรแกรม C สแต็กจะต้องมีขนาดใหญ่พอที่จะเก็บทุกตัวแปรที่ประกาศภายในแต่ละฟังก์ชั่น ฮีปจะเติบโตแบบไดนามิกตามต้องการ แต่ท้ายที่สุดระบบปฏิบัติการก็ทำการโทรออก (มักจะทำให้ฮีพนั้นเติบโตมากกว่าค่าที่ร้องขอโดย malloc ดังนั้นอย่างน้อยในอนาคต mallocs บางตัวก็ไม่จำเป็นต้องกลับไปที่เคอร์เนลเพื่อ ได้รับหน่วยความจำมากขึ้นพฤติกรรมนี้มักจะปรับแต่งได้)

เนื่องจากคุณได้จัดสรรสแต็กก่อนเปิดตัวโปรแกรมคุณไม่จำเป็นต้อง malloc ก่อนที่คุณจะสามารถใช้งานสแต็กได้ ในทางปฏิบัติมันยากมากที่จะคาดการณ์ว่าอะไรจะเร็วและอะไรจะช้าในระบบปฏิบัติการสมัยใหม่ที่มีระบบย่อยหน่วยความจำเสมือนเนื่องจากวิธีการใช้งานหน้าต่างๆ


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

113

ฉันคิดว่าคนอื่น ๆ ให้คำตอบที่ถูกต้องกับคุณเป็นส่วนใหญ่ในเรื่องนี้

อย่างไรก็ตามรายละเอียดที่พลาดไปอย่างหนึ่งคือ "กอง" ควรจะเรียกว่า "ร้านค้าฟรี" เหตุผลของความแตกต่างนี้ก็คือร้านค้าฟรีดั้งเดิมถูกนำไปใช้กับโครงสร้างข้อมูลที่เรียกว่า "binomial heap" ด้วยเหตุผลดังกล่าวการจัดสรรจากการประยุกต์ใช้ในช่วงต้นของ malloc () / free () เป็นการจัดสรรจากฮีพ อย่างไรก็ตามในยุคปัจจุบันร้านค้าฟรีส่วนใหญ่มีการใช้งานกับโครงสร้างข้อมูลที่ซับซ้อนมากซึ่งไม่ใช่ฮีปแบบทวินาม


8
nitpick อีกคำตอบส่วนใหญ่ (เบา ๆ ) บอกเป็นนัยว่าการใช้ "สแต็ค" เป็นสิ่งจำเป็นสำหรับCภาษา นี่เป็นความเข้าใจผิดที่พบบ่อยแม้ว่าจะเป็นกระบวนทัศน์ที่มีอิทธิพลต่อการดำเนินการC99 6.2.4 automatic storage duration objects(ตัวแปร) ในความเป็นจริงคำว่า "สแต็ค" ไม่ได้ปรากฏอยู่ในC99มาตรฐานภาษา: open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf
Johne

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

91

คุณสามารถทำสิ่งที่น่าสนใจกับสแต็ค ตัวอย่างเช่นคุณมีฟังก์ชั่นเช่นalloca (สมมติว่าคุณสามารถผ่านคำเตือนมากมายที่เกี่ยวข้องกับการใช้งาน) ซึ่งเป็นรูปแบบของ malloc ที่ใช้สแต็กไม่ใช่กองสำหรับหน่วยความจำ

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


พกพาได้allocaอย่างไร? เช่นมันทำงานบน Windows หรือไม่ มันใช้เฉพาะกับระบบปฏิบัติการ Unix หรือไม่
Peter Mortensen

89

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

ฮีปคือพื้นที่ของการจัดสรรหน่วยความจำแบบไดนามิกของหน่วยความจำที่ทำออกมา (การโทร "ใหม่" หรือ "จัดสรร" อย่างชัดเจน) มันเป็นโครงสร้างข้อมูลพิเศษที่สามารถติดตามบล็อกของหน่วยความจำขนาดแตกต่างกันและสถานะการจัดสรร

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


[@TED] ทำไมคุณถึงพูดว่า "บางครั้งพารามิเตอร์ถูกผลักลงบนสแต็ก"? สิ่งที่ฉันรู้ก็คือพวกเขามักจะ คุณช่วยอธิบายเพิ่มเติมได้ไหม?
OmarOthman

1
@OmarOthman - ฉันบอกว่าเพราะมันขึ้นอยู่กับผู้เขียนคอมไพเลอร์ / ล่ามของคุณสิ่งที่เกิดขึ้นเมื่อเรียกรูทีนย่อย พฤติกรรมคลาสสิค Fortran คือการไม่ใช้สแต็คเลย บางภาษารองรับสิ่งที่แปลกใหม่เช่นการผ่านชื่อซึ่งเป็นการทดแทนข้อความได้อย่างมีประสิทธิภาพ
TED

83

จาก WikiAnwser

ซ้อนกัน

เมื่อฟังก์ชั่นหรือวิธีการเรียกใช้ฟังก์ชั่นอื่นซึ่งจะเรียกฟังก์ชั่นอื่นฟังก์ชั่นอื่น ๆ การดำเนินการของฟังก์ชั่นเหล่านั้นยังคงถูกระงับจนกว่าฟังก์ชั่นสุดท้ายจะส่งกลับค่าของมัน

สายของการเรียกใช้ฟังก์ชันที่ถูกระงับนี้คือสแต็กเนื่องจากองค์ประกอบในสแต็ก (การเรียกใช้ฟังก์ชัน) ขึ้นอยู่กับแต่ละอื่น ๆ

สแต็กเป็นสิ่งสำคัญที่ต้องพิจารณาในการจัดการข้อยกเว้นและการดำเนินการเธรด

กอง

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


"ฉันชอบคำตอบที่ได้รับการยอมรับดีกว่าเพราะระดับต่ำยิ่งกว่าเดิม" นั่นเป็นสิ่งที่ไม่ดีไม่ใช่สิ่งที่ดี
Lightness Races ในวงโคจร

54

ซ้อนกัน

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

กอง

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

50

ตกลงเพียงและในระยะสั้นพวกเขาหมายถึงสั่งและไม่สั่ง ... !

สแต็ค : ในไอเท็มสแต็คสิ่งต่าง ๆ อยู่ข้างบนซึ่งกันและกันหมายความว่าจะเร็วขึ้นและมีประสิทธิภาพมากขึ้นในการประมวลผล! ...

ดังนั้นจึงมีดัชนีที่ชี้รายการเฉพาะเสมอและการประมวลผลจะเร็วขึ้นมีความสัมพันธ์ระหว่างรายการเช่นกัน! ...

ฮีป : ไม่มีคำสั่งการประมวลผลจะช้ากว่าและค่าจะถูกทำให้สับสนพร้อมกับคำสั่งหรือดัชนีที่เฉพาะเจาะจง ... มีการสุ่มและไม่มีความสัมพันธ์ระหว่างพวกเขา ... ดังนั้นการดำเนินการและเวลาการใช้งานอาจแตกต่างกัน ...

ฉันยังสร้างภาพด้านล่างเพื่อแสดงว่าพวกเขามีลักษณะอย่างไร:

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


49

ในระยะสั้น

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


ในรายละเอียด

สแต็ค

สแต็คเป็นโครงสร้างข้อมูล "LIFO" (เข้าก่อนออกก่อน) ที่ได้รับการจัดการและปรับให้เหมาะสมโดย CPU อย่างใกล้ชิด ทุกครั้งที่ฟังก์ชั่นประกาศตัวแปรใหม่มันจะ "ผลัก" ไปยังสแต็ก จากนั้นทุกครั้งที่ออกจากฟังก์ชันตัวแปรทั้งหมดที่ถูกผลักลงบนสแต็กโดยฟังก์ชันนั้นจะถูกปล่อยให้เป็นอิสระ (กล่าวคือพวกมันถูกลบ) เมื่อตัวแปรสแต็กถูกปลดปล่อยพื้นที่หน่วยความจำนั้นจะพร้อมใช้งานสำหรับตัวแปรสแต็กอื่น ๆ

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

เพิ่มเติมสามารถพบได้ที่นี่


กอง

ฮีปเป็นส่วนหนึ่งของหน่วยความจำของคอมพิวเตอร์ที่ไม่ได้จัดการโดยอัตโนมัติสำหรับคุณและไม่ได้รับการจัดการโดย CPU อย่างแน่นหนา เป็นพื้นที่หน่วยความจำลอยตัวที่ว่างมากขึ้น (และใหญ่กว่า) ในการจัดสรรหน่วยความจำบนฮีปคุณต้องใช้ malloc () หรือ calloc () ซึ่งเป็นฟังก์ชัน C ในตัว เมื่อคุณจัดสรรหน่วยความจำบนฮีปคุณจะต้องรับผิดชอบในการใช้ฟรี () เพื่อยกเลิกการจัดสรรหน่วยความจำนั้นเมื่อคุณไม่ต้องการใช้อีกต่อไป

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

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

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

เพิ่มเติมสามารถพบได้ที่นี่


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

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

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

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

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

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

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

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

แม้รายละเอียดเพิ่มเติมจะได้รับที่นี่และที่นี่


ตอนนี้มาถึงคำตอบของคำถามของคุณ

มีการควบคุมโดย OS หรือรันไทม์ภาษาในระดับใด

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

เพิ่มเติมสามารถพบได้ที่นี่

ขอบเขตของพวกเขาคืออะไร?

ได้รับแล้วในด้านบน

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

เพิ่มเติมสามารถพบได้ในที่นี่

อะไรเป็นตัวกำหนดขนาดของแต่ละคน

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

อะไรทำให้เร็วขึ้น

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

นอกจากนี้ stack vs. heap ไม่เพียง แต่คำนึงถึงประสิทธิภาพเท่านั้น มันยังบอกคุณมากเกี่ยวกับอายุการใช้งานของวัตถุที่คาดหวัง

รายละเอียดสามารถพบได้จากที่นี่


36

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

โปรแกรม C ทั่วไปถูกจัดวางในหน่วยความจำพร้อมโอกาสเพิ่มโดยการเปลี่ยนค่า brk () โดยทั่วไป HEAP ต่ำกว่าค่า brk นี้และ brk ที่เพิ่มขึ้นจะเพิ่มปริมาณฮีปที่พร้อมใช้งาน

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

หนึ่งบล็อกหน่วยความจำทั่วไปคือ BSS (บล็อกของค่าศูนย์) ซึ่งไม่ได้ตั้งใจให้เป็นศูนย์ในข้อเสนอของผู้ผลิตรายหนึ่ง อีกอย่างคือข้อมูลที่มีค่าเริ่มต้นรวมถึงสตริงและตัวเลข ที่สามคือ CODE ที่มี CRT (C runtime), main, function, และ libraries

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

รูปแบบทั่วไปของหน่วยความจำโปรแกรม UNIX C ในปี 1980



26

สองสามเซ็นต์: ฉันคิดว่ามันจะดีในการวาดกราฟิกหน่วยความจำและง่ายขึ้น:

นี่คือวิสัยทัศน์ของฉันในการสร้างหน่วยความจำกระบวนการด้วยความเรียบง่ายเพื่อความเข้าใจที่ง่ายขึ้นเมื่อเกิดอะไรขึ้น


ลูกศร - แสดงตำแหน่งที่เติบโตสแต็คและฮีปขนาดสแต็กกระบวนการมีขีด จำกัด กำหนดไว้ในระบบปฏิบัติการ จำกัด ขนาดสแต็กของเธรดโดยพารามิเตอร์ในเธรดสร้าง API โดยปกติ โดยปกติฮีปจะถูก จำกัด ด้วยขนาดหน่วยความจำเสมือนสูงสุดของกระบวนการเช่น 32 บิต 2-4 GB

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

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


23

เนื่องจากมีคำตอบบางอย่างเกี่ยวกับการวางยาฉันจะให้เงินกับฉัน

น่าแปลกที่ไม่มีใครได้บอกว่าหลาย ๆ (คือไม่เกี่ยวข้องกับจำนวนของการทำงานหัวข้อ OS ระดับ) สแต็คโทรที่จะพบได้เฉพาะในภาษาที่แปลกใหม่ (PostScript) หรือแพลตฟอร์ม (Intel Itanium) แต่ยังอยู่ในเส้นใย , หัวข้อสีเขียวและการใช้งานของบางส่วนcoroutines

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

ไม่ว่าในกรณีใดวัตถุประสงค์ของทั้งไฟเบอร์เธรดสีเขียวและ coroutines นั้นมีหลายฟังก์ชันที่ทำงานพร้อมกัน แต่ไม่ขนานกัน (ดูคำถาม SO นี้สำหรับความแตกต่าง) ภายในเธรดระดับ OS เดี่ยวโอนการควบคุมกลับไปกลับมา ในรูปแบบที่เป็นระเบียบ

เมื่อใช้เส้นใยด้ายสีเขียวหรือ coroutines คุณมักจะมีสแต็คแยกต่างหากต่อฟังก์ชั่น (โดยทางเทคนิคแล้วไม่เพียง แต่เป็นสแต็ก แต่บริบททั้งหมดของการดำเนินการเป็นต่อฟังก์ชั่นที่สำคัญที่สุดคือการลงทะเบียน CPU) สำหรับทุกเธรดที่มีสแต็คจำนวนมากเท่าที่มีการทำงานพร้อมกัน ตามตรรกะของโปรแกรมของคุณ เมื่อฟังก์ชั่นทำงานจนจบสแต็กของมันจะถูกทำลาย ดังนั้นจำนวนและอายุการใช้งานของสแต็คจึงเป็นแบบไดนามิกและไม่ได้พิจารณาจากจำนวนเธรดระดับ OS!

โปรดทราบว่าฉันพูดว่า " มักจะมีสแต็กแยกต่างหากต่อฟังก์ชั่น" มีการใช้งานของ couroutines ทั้งแบบซ้อนและแบบซ้อน ส่วนใหญ่ที่โดดเด่น stackful C ++ การใช้งานที่มีBoost.Coroutineและไมโครซอฟท์ PPLasync/await 's (อย่างไรก็ตามฟังก์ชั่นที่กลับมาใช้ใหม่ของ C ++ (aka " asyncและawait") ซึ่งถูกเสนอให้กับ C ++ 17 มีแนวโน้มที่จะใช้ coroutines แบบไม่ซ้อนกัน)

ข้อเสนอ Fibers สำหรับไลบรารีมาตรฐาน C ++ กำลังจะมีขึ้น นอกจากนี้ยังมีห้องสมุดบุคคลที่สามอยู่ หัวข้อสีเขียวเป็นที่นิยมอย่างมากในภาษาต่างๆเช่น Python และ Ruby


19

ฉันมีบางอย่างที่จะแบ่งปันแม้ว่าประเด็นหลักจะได้รับการคุ้มครองแล้ว

ซ้อนกัน

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

กอง

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

หมายเหตุที่น่าสนใจ:

  • หากการเรียกฟังก์ชั่นถูกเก็บไว้ในกองมันจะส่งผลให้ 2 คะแนนยุ่ง:
    1. เนื่องจากหน่วยเก็บตามลำดับในสแต็กการดำเนินการจึงเร็ว การจัดเก็บในฮีปจะทำให้สิ้นเปลืองเวลาอย่างมากทำให้โปรแกรมทั้งหมดทำงานช้าลง
    2. หากฟังก์ชั่นถูกจัดเก็บในฮีป (หน่วยความจำยุ่งชี้โดยตัวชี้) จะไม่มีทางกลับไปที่ที่อยู่ของผู้โทรกลับ

1
รัดกุมและสะอาด ดี :)
ingconti

13

ว้าว! คำตอบมากมายและฉันไม่คิดว่าหนึ่งในนั้นถูกต้อง ...

1) พวกเขาอยู่ที่ไหนและอะไร (ทางกายภาพในหน่วยความจำของคอมพิวเตอร์จริง)?

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

มีสองกองคือสาธารณะและส่วนตัว

ฮีปส่วนตัวเริ่มต้นที่ขอบเขต 16 ไบต์ (สำหรับโปรแกรม 64 บิต) หรือขอบเขต 8 ไบต์ (สำหรับโปรแกรม 32 บิต) หลังจากไบต์สุดท้ายของรหัสในโปรแกรมของคุณแล้วเพิ่มมูลค่าจากที่นั่น มันจะเรียกว่าฮีปเริ่มต้น

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

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

2) สิ่งที่พวกเขาควบคุมโดยระบบปฏิบัติการหรือรันไทม์ภาษา?

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

2b) ขอบเขตของพวกเขาคืออะไร?

พวกเขาเป็นโปรแกรมระดับโลกทั้งหมด แต่เนื้อหาของพวกเขาอาจเป็นแบบส่วนตัวสาธารณะหรือทั่วโลก

2c) อะไรเป็นตัวกำหนดขนาดของแต่ละรายการ

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

2d) อะไรทำให้เร็วขึ้น?

พวกมันไม่ได้ถูกออกแบบมาให้เร็วมันถูกออกแบบมาให้มีประโยชน์ โปรแกรมเมอร์ใช้ประโยชน์อย่างไรพิจารณาว่าเป็น "เร็ว" หรือ "ช้า"

REF:

https://norasandler.com/2019/02/18/Write-a-Compiler-10.html

https://docs.microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-getprocessheap

https://docs.microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-heapcreate


8

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

ในสแต็กคุณจะบันทึกที่อยู่ผู้ส่งและการเรียก→ push / ret → pop ได้รับการจัดการโดยตรงในฮาร์ดแวร์

คุณสามารถใช้สแต็กเพื่อส่งพารามิเตอร์ .. แม้ว่ามันจะช้ากว่าการใช้รีจิสเตอร์ (กูรูของไมโครโปรเซสเซอร์จะพูดหรือหนังสือไบออสยุค 80 ที่ดีจะ ... )

  • หากไม่มีสแต็คจะไม่มีไมโครโปรเซสเซอร์ทำงานได้ (เราไม่สามารถจินตนาการโปรแกรมแม้แต่ภาษาแอสเซมบลีโดยไม่มีรูทีนย่อย / ฟังก์ชั่น)
  • โดยไม่ต้องกองมันสามารถ (โปรแกรมภาษาแอสเซมบลีสามารถทำงานได้โดยไม่ต้องเนื่องจากฮีปเป็นแนวคิด OS เป็น malloc นั่นคือการเรียก OS / Lib

การใช้สแต็กเร็วกว่า:

  • เป็นฮาร์ดแวร์และแม้แต่การกด / ป๊อปก็มีประสิทธิภาพมาก
  • malloc ต้องการเข้าสู่โหมดเคอร์เนลใช้ล็อค / เซมาฟอร์ (หรือการซิงโครไนซ์ดั้งเดิม) เรียกใช้งานโค้ดบางอย่างและจัดการโครงสร้างบางอย่างที่จำเป็นเพื่อติดตามการจัดสรร

OPP คืออะไร คุณหมายถึง OOP ( object-oriented_programming ) หรือไม่
Peter Mortensen

คุณหมายถึงพูดว่าmallocเป็นการเรียกเคอร์เนลหรือไม่?
Peter Mortensen

1) ใช่ขอโทษ .. OOP ... 2) malloc: ฉันเขียนเร็ว ๆ นี้ขอโทษ ... malloc อยู่ในพื้นที่ของผู้ใช้ .. แต่สามารถเรียกใช้สายอื่น ๆ ได้ .... ประเด็นคือการใช้ฮีปอาจช้ามาก ...
ingconti

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

1

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

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

ที่มา: Academind


0

ขอบคุณสำหรับการสนทนาที่ดีจริงๆ แต่ในฐานะ noob จริงฉันสงสัยว่าจะเก็บคำแนะนำไว้ที่ไหน? ในนักวิทยาศาสตร์ BEGINNING กำลังตัดสินใจระหว่างสถาปัตยกรรมสองแห่ง (von NEUMANN ซึ่งทุกอย่างถูกพิจารณาว่าเป็น DATA และ HARVARD ซึ่งพื้นที่หน่วยความจำถูกสงวนไว้สำหรับคำแนะนำและอีกอันสำหรับข้อมูล) ในที่สุดเราไปกับการออกแบบของฟอนนอยมันน์และตอนนี้ทุกอย่างก็ถือว่าเหมือนกัน สิ่งนี้ทำให้มันยากสำหรับฉันเมื่อฉันเรียนรู้การชุมนุม https://www.cs.virginia.edu/~evans/cs216/guides/x86.html เพราะพวกเขาพูดถึงการลงทะเบียนและตัวชี้สแต็ก

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

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