อธิบายแนวคิดของกรอบสแต็คแบบสั้น ๆ


200

ดูเหมือนว่าฉันได้รับแนวคิดของcall stackในการออกแบบภาษาโปรแกรม แต่ฉันไม่สามารถหาได้ (อาจเป็นไปได้ว่าฉันแค่ค้นหาไม่หนักพอ) คำอธิบายที่เหมาะสมเกี่ยวกับสแต็คเฟรมคืออะไร

ดังนั้นฉันอยากจะขอให้ใครบางคนอธิบายให้ฉันด้วยคำไม่กี่คำ

คำตอบ:


195

กรอบสแต็กเป็นเฟรมของข้อมูลที่ถูกส่งไปยังสแต็ก ในกรณีของ call stack เฟรมสแต็กจะแทนการเรียกใช้ฟังก์ชันและข้อมูลอาร์กิวเมนต์

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

แก้ไข:

มีความแตกต่างอย่างมากระหว่างสแต็กการโทรระดับสูงและการสแต็กการเรียกของโปรเซสเซอร์

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


6
"โปรเซสเซอร์รู้จำนวนไบต์ในแต่ละเฟรมและเลื่อนตัวชี้สแต็กตามที่เฟรมถูกผลักและแตกออกจากสแต็ก" - ฉันสงสัยว่าตัวประมวลผลรู้อะไรเกี่ยวกับสแต็กเพราะเราจัดการมันผ่าน subbing (การจัดสรร) การผลักและ popping และนี่คือการเรียกประชุมที่อธิบายว่าเราควรใช้สแต็คได้อย่างไร
Victor Polevoy

78

ถ้าคุณเข้าใจ stack ได้ดีแล้วคุณจะเข้าใจว่าหน่วยความจำทำงานอย่างไรในโปรแกรมและถ้าคุณเข้าใจว่าหน่วยความจำทำงานอย่างไรในโปรแกรมคุณจะเข้าใจว่า function store ในโปรแกรมและถ้าคุณเข้าใจว่า function store ในโปรแกรมคุณจะเข้าใจว่า คุณเข้าใจว่าฟังก์ชั่น recursive ทำงานอย่างไรคุณจะเข้าใจว่าคอมไพเลอร์ทำงานอย่างไรและถ้าคุณเข้าใจว่าคอมไพเลอร์ทำงานอย่างไรใจของคุณจะทำงานเป็นคอมไพเลอร์และคุณจะดีบักโปรแกรมใด ๆ ได้อย่างง่ายดาย

ให้ฉันอธิบายว่าสแต็คทำงานอย่างไร:

ก่อนอื่นคุณต้องรู้วิธีการแสดงฟังก์ชั่นในกองซ้อน:

กองเก็บค่าที่ปันส่วนแบบไดนามิก
สแต็คเก็บค่าการจัดสรรและการลบอัตโนมัติ

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

มาทำความเข้าใจกับตัวอย่าง:

def hello(x):
    if x==1:
        return "op"
    else:
        u=1
        e=12
        s=hello(x-1)
        e+=1
        print(s)
        print(x)
        u+=1
    return e

hello(4)

ตอนนี้เข้าใจบางส่วนของโปรแกรมนี้:

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

ทีนี้มาดูสแต็คคืออะไรและอะไรคือสแต็กชิ้นส่วน:

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

การจัดสรรสแต็ก:

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

ดูสิ่งนี้ในทางปฏิบัติ:

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

การยกเลิกการจัดสรรบล็อก:

ดังนั้นเมื่อใดก็ตามที่ฟังก์ชันพบคำสั่ง return มันจะลบเฟรมปัจจุบันออกจากสแต็ก

ในขณะที่ส่งคืนจากสแต็กค่าจะส่งกลับในลำดับเดิมที่ถูกจัดสรรในสแต็ก

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


3
สแต็กเติบโตลงและกองเติบโตขึ้นคุณมีพวกเขากลับในแผนภาพของคุณ แก้ไข DIAGRAM ที่นี่
Rafael

@ ราฟาเอลขอโทษด้วยความสับสนฉันกำลังพูดถึงทิศทางการเติบโตฉันไม่ได้พูดถึงทิศทางของการเติบโตแบบกองซ้อน มีความแตกต่างระหว่างทิศทางการเติบโตและทิศทางการเติบโตของกองซ้อน ดูที่นี่ stackoverflow.com/questions/1677415/…
Aaditya Ura

2
ราฟาเอลพูดถูก ภาพแรกก็ผิดเช่นกัน แทนที่ด้วยอย่างอื่น (ค้นหารูปภาพของ Google สำหรับ "heap stack")
Nikos

ดังนั้นถ้าฉันเข้าใจอย่างถูกต้องในแผนภาพที่สามของคุณมี 3 เฟรมสแต็คเพราะhello()เรียกซ้ำแล้วซ้ำอีกhello()ซึ่งเรียกอีกครั้ง (อีกครั้ง) เรียกซ้ำhello()และเฟรมทั่วโลกเป็นฟังก์ชั่นดั้งเดิมซึ่งเรียกว่าครั้งแรกhello()?
Andy J

1
ลิงค์ไหนนำเราไปสู่ ​​?? เนื่องจากความกังวลอย่างมากเกี่ยวกับความปลอดภัยลิงก์เหล่านี้ควรถูกลบโดยเร็วที่สุด
Shivanshu

45

สรุปอย่างรวดเร็ว อาจมีบางคนมีคำอธิบายที่ดีกว่า

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

ในการใช้สแต็กเฟรมเธรดจะเก็บตัวชี้ไว้สองตัวตัวหนึ่งเรียกว่า Stack Pointer (SP) และอีกตัวเรียกว่า Frame Pointer (FP) SP จะชี้ไปที่ "ด้านบน" ของสแต็กเสมอและ FP จะชี้ไปที่ "ด้านบน" ของเฟรมเสมอ นอกจากนี้เธรดยังคงรักษาตัวนับโปรแกรม (PC) ซึ่งชี้ไปที่คำสั่งถัดไปที่จะดำเนินการ

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

มีข้อกำหนดการโทรที่แตกต่างกันเกี่ยวกับการล้างสแต็ก


7
อย่าลืมว่าที่อยู่ผู้ส่งของรูทีนย่อยอยู่ในกองซ้อน
Tony R

4
Frame Pointer ยังเป็น Base Pointer ใน x86 เทอม
peterchaula

1
ฉันต้องการเน้นว่าตัวชี้เฟรมชี้ไปที่จุดเริ่มต้นของเฟรมสแต็กสำหรับการจุติขั้นตอนที่ใช้งานอยู่ในปัจจุบัน
เซิร์ฟเวอร์ Khalilov

13

"สแตกการโทรประกอบด้วยสแต็กเฟรม ... " -  Wikipedia

เฟรมสแต็กเป็นสิ่งที่คุณวางลงบนสแต็ก มันเป็นโครงสร้างข้อมูลที่มีข้อมูลเกี่ยวกับรูทีนย่อยที่จะเรียก


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

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

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

แต่ภาษาแบบไดนามิกก็มีสายโทรเข้าด้วยใช่ไหม? ฉันหมายถึงถ้าพูดว่า Python ต้องการดำเนินการขั้นตอนบางอย่างข้อมูลเกี่ยวกับกระบวนการนี้จะถูกเก็บไว้ในโครงสร้างของล่าม Python บางอย่างฉันจะแก้ไขไหม? ดังนั้นฉันหมายความว่า call stack มีอยู่ไม่เพียง แต่ในระดับต่ำ
ikostia

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

5

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

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

ตัวอย่างเช่นคอมไพเลอร์ Microsoft Visual Studio 2015 C / C ++ มีตัวเลือกต่อไปนี้ที่เกี่ยวข้องกับstack frames:

  • / Oy (การละเว้นเฟรมตัวชี้)

GCC มีดังต่อไปนี้:

  • -fomit-frame-pointer (อย่าเก็บตัวชี้เฟรมไว้ในรีจิสเตอร์สำหรับฟังก์ชั่นที่ไม่จำเป็นต้องใช้สิ่งนี้หลีกเลี่ยงคำแนะนำในการบันทึก, ตั้งค่าและเรียกคืนพอยน์เตอร์พอยน์เตอร์; )

Intel C ++ Compiler มีดังต่อไปนี้:

  • -fomit-frame-pointer (พิจารณาว่าจะใช้ EBP เป็นการลงทะเบียนวัตถุประสงค์ทั่วไปในการปรับให้เหมาะสมหรือไม่)

ซึ่งมีนามแฝงต่อไปนี้:

  • / Oy

Delphi มีตัวเลือกบรรทัดคำสั่งต่อไปนี้:

  • - $ W + (สร้างเฟรมสแต็ค)

ในแง่ที่เฉพาะเจาะจงนั้นจากมุมมองของคอมไพเลอร์เฟรมสแต็คเป็นเพียงรายการเข้าและออกจากรหัสสำหรับรูทีนที่ดันสมอไปยังสแต็ก - ซึ่งยังสามารถใช้สำหรับการดีบักและสำหรับการจัดการข้อยกเว้น เครื่องมือการดีบั๊กอาจสแกนข้อมูลสแต็กและใช้จุดยึดเหล่านี้เพื่อ backtracing ขณะที่ค้นหาcall sitesในสแต็กเช่นเพื่อแสดงชื่อของฟังก์ชันตามลำดับที่ถูกเรียกแบบลำดับชั้น สำหรับสถาปัตยกรรม Intel มันเป็นpush ebp; mov ebp, espหรือenterสำหรับรายการและmov esp, ebp; pop ebpหรือleaveสำหรับทางออก

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

ในบางกรณีสแต็กเฟรม (โค้ดเข้าและออกสำหรับรูทีน) สามารถละเว้นได้โดยคอมไพเลอร์และตัวแปรจะเข้าถึงได้โดยตรงผ่านตัวชี้สแต็ก (SP / ESP / RSP) แทนตัวชี้ฐานที่สะดวก (BP / ESP / RSP) เงื่อนไขสำหรับการละเว้นของกรอบสแต็กเช่น:

  • ฟังก์ชั่นเป็นฟังก์ชั่นลีฟ (เช่น end-เอนทิตีที่ไม่ได้เรียกฟังก์ชั่นอื่น ๆ );
  • ไม่มีการลอง / สุดท้ายหรือลอง / ยกเว้นหรือการสร้างที่คล้ายกันคือไม่มีการใช้ข้อยกเว้น
  • ไม่มีการเรียกรูทีนด้วยพารามิเตอร์ขาออกบนสแต็ก
  • ฟังก์ชั่นไม่มีพารามิเตอร์
  • ฟังก์ชันไม่มีรหัสชุดประกอบแบบอินไลน์
  • ฯลฯ ...

การข้ามเฟรมสแต็ก (รายการและรหัสออกสำหรับรูทีน) สามารถทำให้โค้ดมีขนาดเล็กลงและเร็วขึ้น แต่ก็อาจส่งผลเสียต่อความสามารถของผู้ debuggers ในการย้อนกลับข้อมูลในสแต็กและแสดงให้โปรแกรมเมอร์ นี่คือตัวเลือกคอมไพเลอร์ที่กำหนดภายใต้เงื่อนไขว่าฟังก์ชันควรมีรายการและรหัสทางออกตัวอย่างเช่น: (a) เสมอ, (b) ไม่เคย, (c) เมื่อจำเป็น (ระบุเงื่อนไข)


-1

Stack frame เป็นข้อมูลที่รวบรวมไว้ที่เกี่ยวข้องกับการเรียกใช้ฟังก์ชัน โดยทั่วไปข้อมูลนี้รวมถึงอาร์กิวเมนต์ที่ส่งไปยังฟังก์ชัน th ตัวแปรโลคัลและตำแหน่งที่จะส่งคืนเมื่อยกเลิก บันทึกการเปิดใช้งานเป็นอีกชื่อหนึ่งของสแต็กเฟรม โครงร่างของเฟรมสแต็กถูกกำหนดใน ABI โดยผู้ผลิตและคอมไพเลอร์ทั้งหมดที่สนับสนุน ISA ต้องเป็นไปตามมาตรฐานนี้อย่างไรก็ตามโครงร่างโครงร่างสามารถพึ่งพาคอมไพเลอร์ได้ โดยทั่วไปขนาดเฟรมสแต็กไม่ จำกัด แต่มีแนวคิดที่เรียกว่า "โซนสีแดง / ป้องกัน" เพื่อให้การโทรของระบบ ... ฯลฯ ดำเนินการโดยไม่รบกวนเฟรมสแต็ก

มี SP เสมอ แต่สำหรับ ABIs บางตัว (ตัวอย่างเช่น ARM และ PowerPC) FP เป็นตัวเลือก อาร์กิวเมนต์ที่จำเป็นต้องวางลงบนสแต็กสามารถถูกตัดทิ้งโดยใช้ SP เท่านั้น ไม่ว่าเฟรมสแต็กจะถูกสร้างขึ้นสำหรับการเรียกใช้ฟังก์ชันหรือไม่นั้นขึ้นอยู่กับชนิดและจำนวนของอาร์กิวเมนต์ตัวแปรโลคัลและวิธีเข้าถึงตัวแปรโลคัลโดยทั่วไป บน ISAs ส่วนใหญ่อันดับแรกจะใช้การลงทะเบียนและหากมีข้อโต้แย้งมากกว่าการลงทะเบียนเพื่อส่งผ่านข้อโต้แย้งเหล่านี้จะถูกวางลงบนสแต็ก (ตัวอย่างเช่น x86 ABI มี 6 รีจิสเตอร์เพื่อส่งผ่านอาร์กิวเมนต์จำนวนเต็ม) ดังนั้นบางครั้งฟังก์ชั่นบางอย่างไม่จำเป็นต้องวางเฟรมสแต็กบนสแต็กเพียงแค่ที่อยู่ผู้ส่งถูกส่งไปยังสแต็ก

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