ทางเลือกใดในการใช้สแต็กเพื่อแสดงความหมายของการเรียกใช้ฟังก์ชัน?


19

เราทุกคนรู้และชื่นชอบที่การเรียกใช้ฟังก์ชันมักจะใช้งานโดยใช้ stack มีเฟรม, ที่อยู่ผู้ส่ง, พารามิเตอร์, ล็อตทั้งหมด

อย่างไรก็ตามสแต็กเป็นรายละเอียดการใช้งาน: การเรียกประชุมอาจทำสิ่งต่าง ๆ (เช่น x86 fastcall ใช้ (ลงทะเบียน), MIPS และผู้ติดตามใช้หน้าต่างลงทะเบียนและอื่น ๆ ) และการเพิ่มประสิทธิภาพสามารถทำได้แม้กระทั่งสิ่งอื่น ๆ การเพิ่มประสิทธิภาพการโทรหาง .. )

แน่นอนว่าการมีคำสั่งกองซ้อนที่สะดวกในหลาย ๆ เครื่อง (VMs เช่น JVM และ CLR แต่ยังมีเครื่องจริงเช่น x86 ด้วย PUSH / POP เป็นต้น) ทำให้สะดวกในการใช้งานสำหรับการเรียกใช้ฟังก์ชั่น แต่ในบางกรณีก็เป็นไปได้ เพื่อตั้งโปรแกรมในลักษณะที่ไม่จำเป็นต้องใช้ call stack (ฉันกำลังคิดเกี่ยวกับสไตล์การส่งผ่านต่อเนื่องที่นี่หรือนักแสดงในระบบส่งข้อความ)

ดังนั้นฉันเริ่มสงสัยว่า: เป็นไปได้หรือไม่ที่จะใช้ semantics call function โดยไม่มี stack หรือดีกว่าโดยใช้โครงสร้างข้อมูลที่แตกต่างกัน (คิวบางทีหรือแผนผังที่เชื่อมโยงกัน)
แน่นอนฉันเข้าใจว่า stack เป็นอย่างมาก สะดวก (มีเหตุผลว่าทำไมมันแพร่หลาย) แต่เมื่อเร็ว ๆ นี้ฉันชนเข้ากับการใช้งานที่ทำให้ฉันสงสัย ..

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

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


Cleanใช้กราฟและเครื่องเขียนกราฟซึ่งจะนำไปใช้งานโดยใช้เครื่องสามกอง แต่มีเนื้อหาที่แตกต่างกว่าปกติ

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

คำตอบ:


19

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

อ้างถึง FORTRAN IV (และก่อนหน้า) และ COBOL เวอร์ชันก่อนหน้าสำหรับตัวอย่างภาษาที่ไม่ต้องการการโทรซ้อน

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

เท่าที่ฉันรู้เครื่องสแต็ค Burroughs B5000 เป็นเครื่องแรกที่มีสแต็กการเรียกฮาร์ดแวร์ เครื่อง B5000 ได้รับการออกแบบจากพื้นดินเพื่อเรียกใช้ ALGOL ซึ่งต้องการการเรียกซ้ำ พวกเขายังมีสถาปัตยกรรม descriptor-based แห่งแรกซึ่งวางรากฐานสำหรับสถาปัตยกรรมความสามารถ

เท่าที่ฉันรู้มันเป็น PDP-6 (ซึ่งขยายตัวเป็น DEC-10) ซึ่งเป็นที่นิยมของ call stack hardware เมื่อชุมชนแฮ็กเกอร์ที่ MIT รับการส่งมอบหนึ่งและพบว่าการดำเนินการ PUSHJ (Push Return Address and Jump) อนุญาตให้รูทีนการพิมพ์ทศนิยมลดลงจาก 50 คำสั่งเป็น 10

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

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

โครงสร้างและการตีความของโปรแกรมคอมพิวเตอร์ของ Abelson & Sussman ได้กล่าวถึงรายละเอียดบางอย่างเกี่ยวกับความต่อเนื่อง


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

มีความเกี่ยวข้องเล็กน้อย: คุณชี้อย่างถูกต้องว่า "หากภาษาไม่อนุญาตให้มีการสอบถามซ้ำ" เกี่ยวกับภาษากับการเรียกซ้ำโดยเฉพาะอย่างยิ่งฟังก์ชั่นที่ไม่เรียกซ้ำ? พวกเขาต้องการสแต็ก "ตามการออกแบบ" หรือไม่?
Lorenzo Dematté

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

@Brendan ไม่จำเป็น (อย่างน้อยนั่นคือจุดประสงค์ทั้งหมดของคำถามของฉัน) "จะกลับไปที่" หรือดีกว่า "จะไปที่ไหนต่อไป" จำเป็นต้องเป็นสแต็กหรือโครงสร้าง LIFO หรือไม่ มันอาจจะเป็นต้นไม้แผนที่คิวหรืออะไรอย่างอื่น?
Lorenzo Dematté

ตัวอย่างเช่นความรู้สึกของลำไส้ของฉันคือ CPS ต้องการเพียงต้นไม้ แต่ฉันไม่แน่ใจและฉันไม่รู้จะดูได้ที่ไหน นั่นเป็นเหตุผลที่ฉันถาม ..
Lorenzo Dematté

6

ไม่สามารถใช้ความหมายของการเรียกใช้ฟังก์ชันได้โดยไม่ต้องใช้ stack บางประเภท เป็นไปได้ที่จะเล่นเกมคำศัพท์ (เช่นใช้ชื่ออื่นเช่น "FILO return buffer")

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

ลองนึกภาพคุณมีฟังก์ชั่นมากมายที่ทุกคนสามารถโทรหากันได้ ณ รันไทม์แต่ละฟังก์ชันต้องทราบตำแหน่งที่จะกลับไปเมื่อออกจากฟังก์ชัน หากมีการfirstโทรsecondคุณจะได้:

second returns to somewhere in first

จากนั้นถ้ามีการsecondโทรthirdคุณ:

third returns to somewhere in second
second returns to somewhere in first

จากนั้นถ้ามีการthirdโทรfourthคุณ:

fourth returns to somewhere in third
third returns to somewhere in second
second returns to somewhere in first

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

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

third returns to somewhere in second
second returns to somewhere in first

โดยทั่วไป; "อรรถศาสตร์การเรียกใช้ฟังก์ชัน" หมายความว่า:

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

สิ่งนี้อธิบายถึงบัฟเฟอร์ FILO / LIFO หรือสแต็ก

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

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

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

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

นอกจากนี้โปรดทราบว่ามีบางสิ่งที่ไม่ปฏิบัติตาม "ความหมายการเรียกใช้ฟังก์ชัน" สิ่งเหล่านี้รวมถึง "ความหมายที่แตกต่างกันอย่างมาก" (เช่นการส่งผ่านต่อเนื่องโมเดลของนักแสดง) และยังรวมถึงส่วนขยายทั่วไปของ "semantics call function" เช่นการทำงานพร้อมกัน (เธรด, เส้นใย, อะไรก็ตาม), setjmp/ longjmp, การจัดการข้อยกเว้น ฯลฯ


ตามคำจำกัดความสแต็กคือคอลเล็กชัน LIFO: เข้าก่อนออกก่อน คิวคือการรวบรวม FIFO
John R. Strohm

ดังนั้นสแต็กเป็นโครงสร้างข้อมูลที่ยอมรับได้เท่านั้น? ถ้าเป็นเช่นนั้นทำไม
Lorenzo Dematté

@ JohnR.Strohm: แก้ไข :-)
Brendan

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

4

ภาษาที่เชื่อมต่อกันด้วยของเล่นXYใช้คิวการโทรและสแต็กข้อมูลสำหรับการดำเนินการ

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

ดังนั้นถ้าเรามีฟังก์ชั่นการเพิ่มองค์ประกอบด้านบนเป็นสองเท่า:

; double dup + ;
// defines 'double' to be composed of 'dup' followed by '+'
// dup duplicates the top element of the data stack
// + pops the top two elements and push their sum

จากนั้นฟังก์ชั่นการเขียน+และdupมีลายเซ็นประเภทสแต็ค / คิวต่อไปนี้:

// X is arbitraty stack, Y is arbitrary queue, ^ is concatenation
+      [X^a^b Y] -> [X^(a + b) Y]
dup    [X^a Y] -> [X^a^a Y]

และขัดแย้งกันdoubleจะมีลักษณะเช่นนี้:

double [X Y] -> [X dup^+^Y]

ดังนั้นในแง่หนึ่ง XY จึงไม่ซ้อนกัน


ว้าวขอบคุณ! ฉันจะดูว่า ... ไม่แน่ใจว่าจริง ๆ แล้วมันใช้กับฟังก์ชั่นการโทร แต่คุ้มค่าดูอยู่แล้ว
Lorenzo Dematté

1
@ Karl Damgaard Asmussen "กดคำที่แต่งไว้ที่ด้านหน้าของคิว" "ผลักหน้า" นั่นไม่ใช่สแต็กใช่ไหม

@ guesttttttt222222222 ไม่จริง callstack จะเก็บพอยน์เตอร์ส่งคืนและเมื่อฟังก์ชันส่งคืน callstack จะถูกดึง คิวการดำเนินการจัดเก็บเฉพาะตัวชี้ไปยังฟังก์ชั่นและเมื่อดำเนินการฟังก์ชั่นต่อไปมันจะขยายไปสู่ความหมายและผลักไปที่ด้านหน้าของคิว ใน XY คิวการดำเนินการเป็นจริง deque เนื่องจากมีการดำเนินการที่ทำงานที่ด้านหลังของคิวการดำเนินการเช่นกัน
Karl Damgaard Asmussen
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.