UPDATE: ฉันชอบคำถามนี้มากฉันทำมันเรื่องของบล็อกของฉัน 18 พฤศจิกายน 2011 ขอบคุณสำหรับคำถามที่ยอดเยี่ยม!
ฉันสงสัยอยู่เสมอ: จุดประสงค์ของกองซ้อนคืออะไร
ฉันถือว่าคุณหมายถึงสแต็คการประเมินของภาษา MSIL ไม่ใช่สแต็กต่อเธรดจริงที่รันไทม์
เหตุใดจึงมีการถ่ายโอนจากหน่วยความจำไปยังสแต็กหรือ "กำลังโหลด" ในทางกลับกันเหตุใดจึงมีการถ่ายโอนจากสแต็คไปยังหน่วยความจำหรือ "การจัดเก็บ" ทำไมไม่เพียงแค่วางมันทั้งหมดไว้ในหน่วยความจำ?
MSIL เป็นภาษา "เครื่องเสมือน" คอมไพเลอร์เช่นคอมไพเลอร์ C # สร้างCILจากนั้นที่คอมไพเลอร์อื่นที่เรียกว่าคอมไพเลอร์ JIT (Just In Time) เปลี่ยน IL เป็นรหัสเครื่องจริงที่สามารถดำเนินการได้
ดังนั้นก่อนอื่นลองตอบคำถาม "ทำไมต้องมี MSIL เลย?" ทำไมไม่เพียงให้คอมไพเลอร์ C # เขียนรหัสเครื่อง?
เพราะมันถูกกว่าที่จะทำแบบนี้ สมมติว่าเราไม่ได้ทำเช่นนั้น สมมติว่าแต่ละภาษาจะต้องมีเครื่องสร้างรหัสของตัวเอง คุณมีภาษาต่าง ๆ ยี่สิบภาษา: C #, JScript .NET , Visual Basic, IronPython , F # ... และสมมติว่าคุณมีโปรเซสเซอร์ที่แตกต่างกันสิบตัว คุณต้องเขียนโค้ดกี่ตัว? 20 x 10 = 200 ตัวสร้างโค้ด นั่นเป็นงานจำนวนมาก ตอนนี้สมมติว่าคุณต้องการเพิ่มโปรเซสเซอร์ใหม่ คุณต้องเขียนตัวสร้างโค้ดขึ้นมายี่สิบครั้งโดยแต่ละภาษา
นอกจากนี้มันเป็นงานที่ยากและอันตราย การเขียนโค้ดที่มีประสิทธิภาพสำหรับชิปที่คุณไม่ใช่ผู้เชี่ยวชาญนั้นเป็นงานที่ยาก! นักออกแบบคอมไพเลอร์เป็นผู้เชี่ยวชาญในการวิเคราะห์ความหมายของภาษาไม่ใช่การจัดสรรลงทะเบียนอย่างมีประสิทธิภาพสำหรับชุดชิปใหม่
ทีนี้สมมติว่าเราทำตามวิธี CIL คุณต้องเขียน CIL กี่ตัว? หนึ่งภาษาต่อหนึ่งภาษา คุณต้องเขียนคอมไพเลอร์ JIT กี่ตัว? หนึ่งตัวต่อโปรเซสเซอร์ ทั้งหมด: 20 + 10 = 30 เครื่องกำเนิดรหัส นอกจากนี้ตัวสร้าง language-to-CIL นั้นเขียนได้ง่ายเนื่องจาก CIL เป็นภาษาที่ง่ายและตัวสร้าง CIL-to-machine-code ก็ง่ายต่อการเขียนเพราะ CIL เป็นภาษาที่ง่าย เรากำจัดความซับซ้อนทั้งหมดของ C # และ VB และ whatnot และ "ลด" ทุกอย่างเป็นภาษาง่าย ๆ ที่ง่ายต่อการเขียนกระวนกระวายใจ
มีภาษากลางช่วยลดต้นทุนการผลิตคอมไพเลอร์ภาษาใหม่อย่างมาก นอกจากนี้ยังช่วยลดต้นทุนในการรองรับชิปใหม่ได้อย่างมาก คุณต้องการสนับสนุนชิปใหม่คุณค้นหาผู้เชี่ยวชาญในชิปนั้นและให้พวกเขาเขียนกระวนกระวายใจ CIL และทำเสร็จแล้ว จากนั้นคุณสนับสนุนภาษาเหล่านั้นทั้งหมดบนชิปของคุณ
ตกลงดังนั้นเราได้พิสูจน์แล้วว่าทำไมเราถึงมี MSIL เพราะการใช้ภาษากลางลดค่าใช้จ่าย ทำไมภาษาจึงเป็น "เครื่องซ้อน"
เนื่องจากเครื่องสแต็กเป็นแนวคิดที่ง่ายมากสำหรับนักเขียนคอมไพเลอร์ภาษาที่จะจัดการ สแต็คเป็นกลไกที่ง่ายและเข้าใจได้ง่ายสำหรับการอธิบายการคำนวณ เครื่องสแต็กเป็นแนวคิดที่ง่ายมากสำหรับผู้เขียนคอมไพเลอร์ของ JIT ที่จะจัดการ การใช้สแต็คเป็นนามธรรมลดความซับซ้อนและดังนั้นอีกครั้งมันลดค่าใช้จ่ายของเรา
คุณถามว่า "ทำไมมีสแต็คเลย" ทำไมไม่เพียงทำทุกอย่างออกจากหน่วยความจำโดยตรง ลองคิดกันดู สมมติว่าคุณต้องการสร้างรหัส CIL สำหรับ:
int x = A() + B() + C() + 10;
สมมติว่าเรามีการประชุมที่ "เพิ่ม", "โทร", "ร้านค้า" และอื่น ๆ จะเอาอาร์กิวเมนต์ของพวกเขาออกจากสแต็คและวางผลลัพธ์ของพวกเขา (ถ้ามี) บนสแต็ก ในการสร้างรหัส CIL สำหรับ C # เราแค่พูดดังนี้:
load the address of x // The stack now contains address of x
call A() // The stack contains address of x and result of A()
call B() // Address of x, result of A(), result of B()
add // Address of x, result of A() + B()
call C() // Address of x, result of A() + B(), result of C()
add // Address of x, result of A() + B() + C()
load 10 // Address of x, result of A() + B() + C(), 10
add // Address of x, result of A() + B() + C() + 10
store in address // The result is now stored in x, and the stack is empty.
ตอนนี้สมมติว่าเราทำมันโดยไม่มีสแต็ก เราจะทำมันด้วยวิธีของคุณซึ่งทุก opcode ใช้เวลาที่อยู่ของตัวถูกดำเนินการและที่อยู่ที่จะเก็บผลของมัน :
Allocate temporary store T1 for result of A()
Call A() with the address of T1
Allocate temporary store T2 for result of B()
Call B() with the address of T2
Allocate temporary store T3 for the result of the first addition
Add contents of T1 to T2, then store the result into the address of T3
Allocate temporary store T4 for the result of C()
Call C() with the address of T4
Allocate temporary store T5 for result of the second addition
...
คุณเห็นว่าสิ่งนี้เป็นอย่างไร รหัสของเรามีขนาดใหญ่มากเนื่องจากเราต้องจัดสรรพื้นที่เก็บข้อมูลชั่วคราวทั้งหมดอย่างชัดเจนตามปกติโดยการประชุมเพียงไปที่สแต็ก ที่แย่กว่านั้นรหัสของเราเองก็มีขนาดใหญ่ขึ้นเรื่อย ๆ เพราะตอนนี้พวกเขาทั้งหมดต้องใช้อาร์กิวเมนต์ที่อยู่ที่พวกเขาจะเขียนผลลัพธ์ลงไปและที่อยู่ของแต่ละตัวถูกดำเนินการ คำสั่ง "เพิ่ม" ที่รู้ว่ามันจะเอาสองสิ่งออกจากสแต็ก คำแนะนำการเพิ่มที่ใช้ตัวถูกดำเนินการสองที่อยู่และที่อยู่ผลลัพธ์จะมีขนาดใหญ่มาก
เราใช้ opcodes สแต็คที่ใช้เพราะกองแก้ปัญหาที่พบบ่อย คือผมต้องการที่จะจัดสรรการจัดเก็บชั่วคราวบางใช้งานได้ในเร็ว ๆ นี้แล้วกำจัดมันได้อย่างรวดเร็วเมื่อฉันทำ โดยการตั้งสมมติฐานว่าเรามีสแต็คในการกำจัดของเราเราสามารถทำให้ opcodes มีขนาดเล็กมากและรหัสสั้นมาก
ปรับปรุง: ความคิดเพิ่มเติมบางอย่าง
อนึ่งแนวคิดของการลดต้นทุนลงอย่างมากโดย (1) specifing เครื่องเสมือน (2) การเขียนคอมไพเลอร์ที่กำหนดเป้าหมายภาษา VM และ (3) การเขียน implementations ของ VM บนฮาร์ดแวร์ที่หลากหลายไม่ใช่แนวคิดใหม่เลย . มันไม่ได้มีต้นกำเนิดมาจาก MSIL, LLVM, Java bytecode หรือโครงสร้างพื้นฐานที่ทันสมัยอื่น ๆ การใช้กลยุทธ์นี้เร็วที่สุดที่ฉันทราบคือเครื่อง pcodeจากปี 1966
สิ่งแรกที่ฉันได้ยินเกี่ยวกับแนวคิดนี้คือเมื่อฉันได้เรียนรู้ว่าผู้วางระบบ Infocom สามารถจัดการให้Zorkทำงานบนเครื่องที่แตกต่างกันมากมายได้อย่างไร พวกเขาระบุเครื่องเสมือนที่เรียกว่าZ-machineจากนั้นสร้าง Z-machine emulators สำหรับฮาร์ดแวร์ทั้งหมดที่พวกเขาต้องการเรียกใช้เกมของพวกเขา สิ่งนี้มีประโยชน์มากมายมหาศาลที่พวกเขาสามารถใช้การจัดการหน่วยความจำเสมือนบนระบบ 8 บิตดั้งเดิม เกมอาจมีขนาดใหญ่เกินกว่าจะใส่ลงในหน่วยความจำได้เพราะพวกเขาสามารถใส่โค้ดจากดิสก์เมื่อพวกเขาต้องการและละทิ้งมันเมื่อพวกเขาต้องการโหลดโค้ดใหม่