ตัวแปรถูกจัดเก็บในและดึงข้อมูลจากสแต็กของโปรแกรมอย่างไร


47

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

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

แต่สิ่งที่ฉันไม่ได้รับก็คือแอปพลิเคชันอ่านแล้วว่าตัวแปรในสแต็กถ้าฉันประกาศและกำหนดxเป็นจำนวนเต็มพูดx = 3และที่เก็บถูกสงวนไว้ในสแต็คจากนั้นค่าของ3มันจะถูกเก็บไว้ที่นั่น ฟังก์ชั่นเดียวกันฉันประกาศและกำหนดyเป็นพูด4แล้วจากนั้นฉันก็ใช้xในการแสดงออกอื่น (พูดz = 5 + x) โปรแกรมสามารถอ่านได้อย่างไรxเพื่อประเมินzเมื่อมันอยู่ด้านล่างyบนกอง? ฉันขาดอะไรบางอย่างชัดเจน มันเป็นที่ตั้งบนสแต็คเป็นเพียงเกี่ยวกับอายุการใช้งาน / ขอบเขตของตัวแปรและว่าสแต็คทั้งหมดสามารถเข้าถึงโปรแกรมได้ตลอดเวลา? ถ้าเป็นเช่นนั้นนั่นหมายความว่ามีบางดัชนีอื่น ๆ ที่เก็บที่อยู่ของตัวแปรในสแต็กเพื่อให้สามารถดึงค่าได้หรือไม่? แต่ฉันคิดว่าจุดทั้งหมดของสแต็คคือค่านั้นถูกเก็บไว้ในที่เดียวกับที่อยู่ตัวแปรหรือไม่ ในใจที่อ่อนแอของฉันดูเหมือนว่าหากมีดัชนีอื่นนี้เราก็กำลังพูดถึงบางสิ่งบางอย่างที่มากกว่ากอง ฉันสับสนอย่างชัดเจนและฉันแค่หวังว่าจะมีคำตอบง่ายๆสำหรับคำถามง่าย ๆ ของฉัน

ขอบคุณที่อ่านมาไกลขนาดนี้


7
@ fade2black ฉันไม่เห็นด้วย - ควรเป็นไปได้ที่จะให้คำตอบของความยาวที่สมเหตุสมผลซึ่งสรุปประเด็นสำคัญ
David Richerby

9
คุณกำลังทำข้อผิดพลาดที่พบบ่อยมากมหันต์ชนิดของค่ากับที่มันจะถูกเก็บไว้ มันก็แค่ผิดที่จะบอกว่าคนโง่ไปบนกอง คนโง่เขลาไปในตัวแปรและตัวแปรจะอยู่ในสแต็กหากรู้ว่าอายุการใช้งานของพวกมันสั้นและอยู่บนฮีปหากอายุการใช้งานของพวกมันไม่สั้น สำหรับความคิดบางอย่างเกี่ยวกับวิธีการที่เกี่ยวข้องกับ C # นี้โปรดดู blogs.msdn.microsoft.com/ericlippert/2010/09/30/…
Eric Lippert

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

5
นอกจากนี้: ฉันขอชมเชยคุณสำหรับความคิดริเริ่มที่จะเรียนรู้สิ่งใหม่ ๆ และเรียนรู้รายละเอียดการใช้ภาษา คุณกำลังใช้งานเป็นอุปสรรค์ที่น่าสนใจที่นี่: คุณเข้าใจสิ่งที่กองเป็นชนิดข้อมูลนามธรรมเป็น แต่ไม่เป็นรายละเอียดการดำเนินงานสำหรับ reifying ยืนยันการใช้งานและความต่อเนื่อง หลังไม่เป็นไปตามกฎของชนิดข้อมูลนามธรรมสแต็ก มันปฏิบัติต่อพวกเขาเป็นแนวทางมากกว่าเป็นกฎ จุดรวมของภาษาโปรแกรมคือเพื่อให้แน่ใจว่าคุณไม่ต้องเข้าใจรายละเอียดที่แยกออกไปเหล่านี้เพื่อแก้ปัญหาการเขียนโปรแกรม
Eric Lippert

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

คำตอบ:


24

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

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

k

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


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

ขอบคุณแก้ไขแล้ว อันที่จริงบางส่วนของ "บล็อก" เหล่านี้เป็นเพียงการลงทะเบียน
Yuval Filmus

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

1
@MikeCaron กองแทบไม่มีอะไรเกี่ยวข้องกับการเรียกซ้ำ เหตุใดคุณจึง "กระจายตัวแปร" ในกลยุทธ์การใช้งานอื่น ๆ
Gardenhead

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

23

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

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

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

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


1
ดูเหมือนว่าคำตอบที่ชัดเจนที่สุดในแง่ของการไม่พูดเกินความรู้พื้นฐานที่ OP นำมาสู่ตาราง +1 สำหรับการกำหนดเป้าหมาย OP จริง ๆ !
Ben I.

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

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

1
@CelineAtwood โปรดทราบว่าการพยายามเข้าถึงตัวแปร "โดยการบังคับ" หลังจากลบออกจากสแต็กจะทำให้คุณไม่สามารถคาดเดาได้ / ไม่ได้กำหนดพฤติกรรมและไม่ควรทำ แจ้งให้ทราบล่วงหน้าฉันไม่ได้พูดว่า "ไม่สามารถ" b / c บางภาษาจะช่วยให้คุณลอง ยังคงเป็นความผิดพลาดในการเขียนโปรแกรมและควรหลีกเลี่ยง
code_dredd

12

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

1. เฟรมสแต็ก

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

เฟรมสแต็ก CSAPP

2. การจัดการเฟรมสแต็กและตำแหน่งตัวแปร

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

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

3. รหัสที่คอมไพเลอร์สร้างขึ้น

แต่สิ่งที่ฉันไม่ได้รับก็คือแอปพลิเคชันอ่านแล้วว่าตัวแปรในสแต็กถ้าฉันประกาศและกำหนด x เป็นจำนวนเต็ม, พูดว่า x = 3, และหน่วยเก็บข้อมูลถูกสงวนไว้ในสแต็ก ตรงนั้นจากนั้นในฟังก์ชั่นเดียวกันฉันก็ประกาศและกำหนด y เป็น, พูด 4, แล้วต่อจากนั้นฉันก็ใช้ x ในนิพจน์อื่น, (พูด z = 5 + x) โปรแกรมจะอ่านค่า x อย่างไรเพื่อประเมิน z เมื่อ มันต่ำกว่า y ในกองหรือไม่

ให้เราใช้โปรแกรมตัวอย่างง่ายๆที่เขียนใน C เพื่อดูว่ามันทำงานอย่างไร:

int main(void)
{
        int x = 3;
        int y = 4;
        int z = 5 + x;

        return 0;
}

ให้เราตรวจสอบข้อความประกอบที่ผลิตโดย GCC สำหรับข้อความต้นฉบับ C นี้ (ฉันทำความสะอาดขึ้นเล็กน้อยเพื่อความชัดเจน):

main:
    pushl   %ebp              # save previous frame's base address on stack
    movl    %esp, %ebp        # use current address of stack pointer as new frame base address
    subl    $16, %esp         # allocate 16 bytes of space on stack for function data
    movl    $3, -12(%ebp)     # variable x at address %ebp - 12
    movl    $4, -8(%ebp)      # variable y at address %ebp - 8
    movl    -12(%ebp), %eax   # write x to register %eax
    addl    $5, %eax          # x + 5 = 9
    movl    %eax, -4(%ebp)    # write 9 to address %ebp - 4 - this is z
    movl    $0, %eax
    leave

สิ่งที่เราสังเกตก็คือว่าตัวแปร X, Y และ Z จะอยู่ที่อยู่%ebp - 12, %ebp -8และ%ebp - 4ตามลำดับ ในคำอื่น ๆสถานที่ของตัวแปรภายในกองกรอบสำหรับการคำนวณโดยใช้ที่อยู่หน่วยความจำที่บันทึกไว้ในการลงทะเบียนของmain() CPU%ebp

4. ข้อมูลในหน่วยความจำเกินกว่าตัวชี้สแต็กอยู่นอกขอบเขต

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

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

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

5. สรุป

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


ของฉันถ้าฉันถามว่าภาพนั้นมาจากไหน มันดูคุ้นเคยอย่างน่าสงสัย ... :-) มันอาจจะเคยเป็นในตำราเรียนมาแล้ว
The Great Duck

1
nvmd ฉันเพิ่งเห็นลิงค์ มันเป็นสิ่งที่ฉันคิด +1 สำหรับการแบ่งปันหนังสือเล่มนั้น
The Great Duck

1
+1 สำหรับการสาธิตชุด gcc :)
flow2k

9

มีการลงทะเบียนพิเศษสองรายการ: ESP (ตัวชี้สแต็ค) และ EBP (ตัวชี้ฐาน) เมื่อโพรซีเดอร์ถูกเรียกใช้การดำเนินการสองครั้งแรกมักจะเป็น

push        ebp  
mov         ebp,esp 

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

Assembler แปลชื่อตัวแปรเป็นค่าชดเชย EBP ตัวอย่างเช่นหากคุณมีตัวแปรเฉพาะที่สองตัวx,yและคุณมีลักษณะคล้ายกัน

  x = 1;
  y = 2;
  return x + y;

จากนั้นมันอาจถูกแปลเป็นสิ่งที่ชอบ

   push        ebp  
   mov         ebp,esp
   mov  DWORD PTR [ ebp + 6],  1   ;x = 1
   mov  DWORD PTR [ ebp + 14], 2   ;y = 2
   mov  eax, [ ebp + 6 ]
   add  [ ebp + 14 ], eax          ; x + y 
   mov  eax, [ ebp + 14 ] 
   ...  

ค่าออฟเซ็ต 6 และ 14 ถูกคำนวณ ณ เวลารวบรวม

นี่เป็นวิธีการทำงานคร่าวๆ อ้างถึงหนังสือรวบรวมสำหรับรายละเอียด


14
นี่คือเฉพาะกับ intel x86 บน ARM ใช้ register SP (R13) เช่นเดียวกับ FP (R11) และใน x86 การไม่ลงทะเบียนหมายความว่าคอมไพเลอร์ที่ก้าวร้าวจะไม่ใช้ EBP เนื่องจากมันมาจาก ESP นี่เป็นตัวอย่างที่ชัดเจนว่าเขาเป็นตัวสุดท้ายซึ่งการแปลที่อยู่แบบ EBP ทั้งหมดสามารถแปลเป็น ESP แบบสัมพันธ์โดยไม่จำเป็นต้องมีการเปลี่ยนแปลงอื่นใด
MSalters

คุณไม่พลาด SUB ใน ESP เพื่อสร้างพื้นที่สำหรับ x และ y ในตอนแรกใช่ไหม
Hagen von Eitzen

@HagenvonEitzen อาจเป็นไปได้ ฉันแค่ต้องการแสดงความคิดเห็นว่าการเข้าถึงตัวแปรที่จัดสรรในสแต็กทำได้อย่างไร
fade2black

Downvoters ความคิดเห็นโปรด !!!
fade2black

8

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

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

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

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

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

ดังนั้นพฤติกรรมของ FILO นี้จะปรากฏเมื่อใด

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

จะเกิดอะไรขึ้นถ้าฟังก์ชั่นส่งคืน? เมื่อกลับมาทำงาน callee ฟังก์ชั่นการโทร, กองกรอบการทำงานของผู้ถูกเรียกจะโผล่ออกมาจากกองพื้นที่พ้นสำหรับใช้ในอนาคต

ดังนั้นจากคำถามของคุณ:

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

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

แล้วกองอะไรที่แตกต่างจากกอง?

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

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


4

มีหลายวิธีในการใช้ตัวแปรโลคัลโดยระบบรันไทม์ภาษา การใช้สแต็กเป็นโซลูชันที่มีประสิทธิภาพทั่วไปซึ่งใช้ในกรณีที่ใช้งานได้หลายอย่าง

สังหรณ์ใจตัวชี้สแต็คspจะถูกเก็บไว้ที่รันไทม์ (ในที่อยู่คงที่หรือในการลงทะเบียน - มันไม่สำคัญ) สมมติว่าทุกครั้งที่ "push" เพิ่มตัวชี้สแต็ก

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

โปรดทราบว่าเรากำลังใช้คำว่า "stack" ที่นี่ในความหมายที่หลวม สแต็คนี้ไม่ได้เข้าถึงได้ผ่านการผลักดัน / POP / การดำเนินงานด้านบน sp - Kแต่ยังมีการเข้าถึงโดยใช้

ยกตัวอย่างเช่นพิจารณารหัสเทียมนี้:

procedure f(int x, int y) {
  print(x,y);    // (1)
  if (...) {
    int z=x+y; // (2)
    print(x,y,z);  // (3)
  }
  print(x,y); // (4)
  return;
}

เมื่อโพรซีเดอร์ถูกเรียกใช้อาร์กิวเมนต์x,yสามารถส่งผ่านบนสแต็ก สำหรับความเรียบง่ายถือว่าการประชุมเป็นดันโทรแรกแล้วxy

แล้วคอมไพเลอร์ที่จุด (1) สามารถหาxที่sp - 2และที่ysp - 1

ณ จุด (2) ตัวแปรใหม่จะถูกนำมาอยู่ในขอบเขต คอมไพเลอร์สร้างรหัสที่ผลรวมx+yคือสิ่งที่ชี้sp - 2และsp - 1และผลักผลลัพธ์ของผลรวมในกอง

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

ที่จุด (4) เราออกจากขอบเขต zเป็น popped และyพบอีกครั้งที่อยู่sp - 1และที่xsp - 2

เมื่อเรากลับมาfผู้โทรอย่างใดอย่างหนึ่งหรือปรากฏขึ้นx,yจากกอง

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

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


3

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

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


2

ไอเท็มข้อมูลที่สามารถไปบนสแต็กได้ถูกวางลงบนสแต็ก - ใช่! มันเป็นพื้นที่พรีเมี่ยม นอกจากนี้เมื่อเราผลักxเข้าไปในกองแล้วผลักyเข้าไปในกองเราจะไม่สามารถเข้าถึงได้xจนกว่าyจะมี เราจำเป็นที่จะปรากฏในการเข้าถึงy xคุณทำให้ถูกต้อง

สแต็กไม่ใช่ตัวแปร แต่เป็นของ frames

ที่คุณได้รับมันผิดเกี่ยวกับสแต็คเอง บนสแต็กไม่ใช่รายการข้อมูลที่ถูกพุชโดยตรง แต่ในสิ่งที่เรียกว่าstack-frameถูกผลัก กรอบสแต็กนี้มีไอเท็มข้อมูล ในขณะที่คุณไม่สามารถเข้าถึงเฟรมที่อยู่ลึกในสแต็กคุณสามารถเข้าถึงเฟรมด้านบนและรายการข้อมูลทั้งหมดที่อยู่ภายใน

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

จบคำตอบ เพิ่มเติม (คุยโว) ในเฟรมเหล่านี้

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

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


ขอบคุณสำหรับการพิจารณา CS ตอนนี้ฉันเป็นโปรแกรมเมอร์ในการเรียนเปียโนเป็นเวลาหลายวัน :)

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