การจัดส่งแบบไดนามิก
ในวิทยาการคอมพิวเตอร์การส่งคำสั่งแบบไดนามิกคือกระบวนการเลือกการใช้งานใดของการดำเนินการแบบโพลีมอร์ฟิก ( เมธอดหรือฟังก์ชัน) ที่จะเรียกใช้ในเวลารันไทม์โดยทั่วไปจะใช้และถือเป็นลักษณะสำคัญของภาษาและระบบการเขียนโปรแกรมเชิงวัตถุ (OOP) [ 1 ]
ระบบเชิงวัตถุจำลองปัญหาเป็นชุดของวัตถุที่โต้ตอบกันซึ่งดำเนินการตามที่อ้างถึงด้วยชื่อ ปรากฏการณ์โพลีมอร์ฟิซึมคือปรากฏการณ์ที่วัตถุที่สามารถใช้แทนกันได้ในระดับหนึ่งแต่ละวัตถุเปิดเผยการดำเนินการที่มีชื่อเดียวกัน แต่อาจมีพฤติกรรมที่แตกต่างกัน ตัวอย่างเช่น วัตถุ ไฟล์และ วัตถุ ฐานข้อมูลต่างก็มี เมธอด StoreRecordที่สามารถใช้เขียนบันทึกข้อมูลบุคลากรลงในที่เก็บข้อมูลได้ การใช้งานของพวกมันแตกต่างกัน โปรแกรมจะเก็บการอ้างอิงถึงวัตถุซึ่งอาจเป็น วัตถุ ไฟล์หรือ วัตถุ ฐานข้อมูลก็ได้ วัตถุนั้นอาจถูกกำหนดโดยการตั้งค่าขณะรันไทม์ และในขั้นตอนนี้ โปรแกรมอาจไม่รู้หรือไม่สนใจว่าจะเป็นวัตถุใด เมื่อโปรแกรมเรียกStoreRecordบนวัตถุ จะต้องมีบางสิ่งเลือกพฤติกรรมที่จะดำเนินการ หากเราคิดว่า OOP คือการส่งข้อความไปยังวัตถุ ในตัวอย่างนี้ โปรแกรมจะส่ง ข้อความ StoreRecordไปยังวัตถุประเภทที่ไม่รู้จัก โดยปล่อยให้ระบบสนับสนุนขณะรันไทม์เป็นผู้ส่งข้อความไปยังวัตถุที่ถูกต้อง วัตถุจะดำเนินการตามพฤติกรรมใดก็ตามที่มันใช้งาน[ 2 ]
การเรียกใช้ฟังก์ชันแบบไดนามิกแตกต่างจากการเรียกใช้ฟังก์ชันแบบคงที่ซึ่งการเลือกการใช้งานของฟังก์ชันแบบพหุรูปจะเกิดขึ้นในระหว่างการคอมไพล์จุดประสงค์ของการเรียกใช้ฟังก์ชันแบบไดนามิกคือการเลื่อนการเลือกการใช้งานที่เหมาะสมออกไปจนกว่าจะทราบชนิดของพารามิเตอร์ (หรือหลายพารามิเตอร์) ในขณะรันไทม์
การเรียกใช้ฟังก์ชันแบบไดนามิก ( Dynamic dispatch) แตกต่างจากการผูกฟังก์ชันแบบล่าช้า (Late binding ) (หรือที่เรียกว่า Dynamic binding เช่นกัน) การ ผูกฟังก์ชันด้วยชื่อ (Name binding) จะเชื่อมโยงชื่อกับฟังก์ชันการทำงาน ฟังก์ชันการทำงานแบบพหุรูป (Polymorphic operation) มีหลายรูปแบบการใช้งาน ซึ่งทั้งหมดเชื่อมโยงกับชื่อเดียวกัน การผูกฟังก์ชันสามารถทำได้ในระหว่างการคอมไพล์ หรือ (ด้วยการผูกฟังก์ชันแบบล่าช้า) ในระหว่างการทำงาน ส่วนการเรียกใช้ฟังก์ชันแบบไดนามิก จะเลือกรูปแบบการใช้งานเฉพาะของฟังก์ชันการทำงานในระหว่างการทำงาน แม้ว่าการเรียกใช้ฟังก์ชันแบบไดนามิกจะไม่หมายความถึงการผูกฟังก์ชันแบบล่าช้า แต่การผูกฟังก์ชันแบบล่าช้าหมายความถึงการเรียกใช้ฟังก์ชันแบบไดนามิก เนื่องจากรูปแบบการใช้งานของฟังก์ชันการทำงานที่ถูกผูกแบบล่าช้าจะไม่เป็นที่รู้จักจนกว่าจะถึงเวลาทำงาน
กลไก
การจัดส่งแบบเดี่ยวและแบบหลายรายการ
การเลือกเวอร์ชันของเมธอดที่จะเรียกใช้นั้น อาจขึ้นอยู่กับอ็อบเจ็กต์เดียว หรือการรวมกันของอ็อบเจ็กต์หลายตัว แบบแรกเรียกว่าsingle dispatchและได้รับการสนับสนุนโดยตรงจากภาษาโปรแกรมเชิงวัตถุทั่วไป เช่นSmalltalk , C++ , Java , C# , Objective-C , Swift , JavaScriptและPythonในภาษาเหล่านี้และภาษาที่คล้ายคลึงกัน เราสามารถเรียกใช้เมธอดสำหรับการหารด้วยไวยากรณ์ที่คล้ายกับ
ตัวตั้งหาร. หาร( ตัวหาร) # ตัวตั้งหาร / ตัวหารโดยที่พารามิเตอร์เป็นตัวเลือก นี่เปรียบเสมือนการส่งข้อความชื่อdivideพร้อมพารามิเตอร์divisorไปยังdividendการเลือกวิธีการใช้งานจะขึ้นอยู่กับประเภทของdividend เท่านั้น (อาจเป็น จำนวนตรรกยะจำนวนทศนิยมหรือเมทริกซ์ ) โดยไม่คำนึงถึงประเภทหรือค่าของdivisor
ในทางตรงกันข้าม บางภาษาจะเลือกใช้เมธอดหรือฟังก์ชันโดยพิจารณาจากลักษณะของตัวถูกดำเนินการ ในกรณีของการหาร ชนิดของตัวตั้งหารและตัวหาร จะ ร่วมกันกำหนดว่า ควรดำเนินการ หารแบบ ใด ซึ่งเรียกว่า การเลือก ใช้หลายฟังก์ชัน ( multiple dispatch ) ตัวอย่างของภาษาที่รองรับการเลือกใช้หลายฟังก์ชัน ได้แก่Common Lisp , DylanและJulia
กลไกการจัดส่งแบบไดนามิก
ภาษาโปรแกรมหนึ่งๆ อาจถูกนำไปใช้ด้วยกลไกการเรียกใช้ฟังก์ชันแบบไดนามิกที่แตกต่างกัน ทางเลือกของกลไกการเรียกใช้ฟังก์ชันแบบไดนามิกที่ภาษาโปรแกรมนั้นๆ นำเสนอ จะเปลี่ยนแปลงรูปแบบการเขียนโปรแกรมที่มีให้เลือกใช้ หรือรูปแบบที่เหมาะสมที่สุดที่จะใช้ในภาษาโปรแกรมนั้นๆ อย่างมาก
โดยปกติ ในภาษาที่มีการกำหนดประเภทข้อมูล กลไกการส่งคำสั่งจะทำงานโดยพิจารณาจากประเภทของอาร์กิวเมนต์ (โดยส่วนใหญ่มักพิจารณาจากประเภทของตัวรับข้อความ) ภาษาที่มีระบบการกำหนดประเภทข้อมูลที่อ่อนแอหรือไม่กำหนดประเภทข้อมูลเลย มักจะมีตารางการส่งคำสั่งเป็นส่วนหนึ่งของข้อมูลอ็อบเจ็กต์สำหรับแต่ละอ็อบเจ็กต์ ซึ่งช่วยให้สามารถกำหนดพฤติกรรมแบบอินสแตนซ์ได้เนื่องจากแต่ละอินสแตนซ์อาจแมปข้อความที่กำหนดไปยังเมธอดที่แตกต่างกัน
บางภาษานำเสนอแนวทางแบบผสมผสาน
การเรียกใช้ฟังก์ชันแบบไดนามิกจะทำให้เกิดค่าใช้จ่ายเพิ่มเติมเสมอ ดังนั้นบางภาษาจึงมีวิธีการเรียกใช้ฟังก์ชันแบบคงที่สำหรับบางเมธอด
การใช้งาน C++
C++ ใช้การผูกแบบเร็ว (early binding) และมีทั้งการเรียกใช้ฟังก์ชันแบบไดนามิก (dynamic) และแบบสแตติก (static) รูปแบบการเรียกใช้ฟังก์ชันเริ่มต้นคือแบบสแตติก หากต้องการเรียกใช้ฟังก์ชันแบบไดนามิก โปรแกรมเมอร์ต้องประกาศเมธอดเป็นแบบเสมือน (virtual )
โดยทั่วไป คอมไพเลอร์ C++ จะใช้โครงสร้างข้อมูลที่เรียกว่าตารางฟังก์ชันเสมือน (vtable) ในการจัดการการเรียกใช้ฟังก์ชันแบบไดนามิก ซึ่งกำหนดการแมปชื่อไปยังการใช้งานสำหรับคลาสที่กำหนดในรูปแบบของตัวชี้ฟังก์ชันสมาชิก นี่เป็นเพียงรายละเอียดการใช้งานเท่านั้น เนื่องจากข้อกำหนดของ C++ ไม่ได้กล่าวถึง vtable อินสแตนซ์ของประเภทนั้นจะจัดเก็บตัวชี้ไปยังตารางนี้เป็นส่วนหนึ่งของข้อมูลอินสแตนซ์ ซึ่งทำให้สถานการณ์ซับซ้อนขึ้นเมื่อ ใช้ การสืบทอดแบบหลายทางเนื่องจาก C++ ไม่รองรับการผูกแบบล่าช้า (late binding) ตารางเสมือนในออบเจ็กต์ C++ จึงไม่สามารถแก้ไขได้ในขณะรันไทม์ ซึ่งจำกัดชุดเป้าหมายการเรียกใช้ฟังก์ชันที่เป็นไปได้ให้เหลือเพียงชุดจำนวนจำกัดที่เลือกไว้ในขณะคอมไพล์
การโอเวอร์โหลดชนิดข้อมูล (Type overloading) ไม่ก่อให้เกิดการเรียกใช้ฟังก์ชันแบบไดนามิก (dynamic dispatch) ในภาษา C++ เนื่องจากภาษาถือว่าชนิดของพารามิเตอร์ข้อความเป็นส่วนหนึ่งของชื่อข้อความอย่างเป็นทางการ นั่นหมายความว่าชื่อข้อความที่โปรแกรมเมอร์เห็นนั้นไม่ใช่ชื่ออย่างเป็นทางการที่ใช้สำหรับการผูกฟังก์ชัน (binding)
การใช้งาน Go, Rust และ Nim
ในภาษา Go , RustและNimมีการใช้รูปแบบการผูกข้อมูลล่วงหน้าที่ยืดหยุ่นกว่า ตัวชี้ Vtable จะถูกส่งไปพร้อมกับการอ้างอิงวัตถุในฐานะ 'ตัวชี้อ้วน' ('อินเทอร์เฟซ' ใน Go หรือ 'วัตถุลักษณะเฉพาะ' ใน Rust [ 3 ] [ 4 ] )
วิธีการนี้ช่วยแยกส่วนต่อประสานที่รองรับออกจากโครงสร้างข้อมูลพื้นฐาน ไลบรารีที่คอมไพล์แล้วแต่ละตัวไม่จำเป็นต้องรู้ส่วนต่อประสานที่รองรับทั้งหมดเพื่อที่จะใช้งานประเภทได้อย่างถูกต้อง เพียงแค่รู้เค้าโครง vtable เฉพาะที่ต้องการก็พอแล้ว โค้ดสามารถส่งผ่านส่วนต่อประสานที่แตกต่างกันไปยังข้อมูลชิ้นเดียวกันในฟังก์ชันต่างๆ ได้ ความยืดหยุ่นนี้มาพร้อมกับข้อเสียคือต้องมีข้อมูลเพิ่มเติมในแต่ละการอ้างอิงวัตถุ ซึ่งเป็นปัญหาหากมีการจัดเก็บการอ้างอิงดังกล่าวจำนวนมากอย่างถาวร
คำว่า"fat pointer"หมายถึงพอยเตอร์ที่มีข้อมูลเพิ่มเติมที่เกี่ยวข้อง ข้อมูลเพิ่มเติมนั้นอาจเป็นพอยเตอร์ vtable สำหรับการเรียกใช้ฟังก์ชันแบบไดนามิกตามที่อธิบายไว้ข้างต้น แต่โดยทั่วไปแล้วจะเป็นขนาดของวัตถุที่เกี่ยวข้องเพื่ออธิบาย เช่นsliceเป็นต้น
การใช้งาน Smalltalk
Smalltalk ใช้ตัวจัดการข้อความแบบอิงตามประเภทข้อมูล แต่ละอินสแตนซ์จะมีประเภทข้อมูลเดียวซึ่งคำจำกัดความของประเภทข้อมูลนั้นประกอบด้วยเมธอดต่างๆ เมื่ออินสแตนซ์ได้รับข้อความ ตัวจัดการข้อความจะค้นหาเมธอดที่ตรงกันในแผนที่ข้อความไปยังเมธอดสำหรับประเภทข้อมูลนั้น แล้วจึงเรียกใช้เมธอดดังกล่าว
เนื่องจากชนิดข้อมูลหนึ่งๆ สามารถมีลำดับชั้นของชนิดข้อมูลพื้นฐานได้ การค้นหาจึงอาจมีค่าใช้จ่ายสูง การใช้งานกลไกของ Smalltalk อย่างง่ายๆ ดูเหมือนจะมีค่าใช้จ่ายสูงกว่าของ C++ อย่างมาก และค่าใช้จ่ายนี้จะเกิดขึ้นทุกครั้งที่วัตถุได้รับข้อความ
การใช้งาน Smalltalk จริงมักใช้เทคนิคที่เรียกว่าinline caching [ 5 ]ซึ่งทำให้การเรียกใช้เมธอดรวดเร็วมาก Inline caching จะเก็บที่อยู่ของเมธอดปลายทางก่อนหน้าและคลาสของอ็อบเจ็กต์ของไซต์การเรียกใช้ (หรือหลายคู่สำหรับการแคชแบบหลายทาง) เมธอดที่แคชไว้จะถูกเริ่มต้นด้วยเมธอดเป้าหมายที่พบบ่อยที่สุด (หรือเพียงแค่ตัวจัดการแคชพลาด) โดยอิงตามตัวเลือกเมธอด เมื่อถึงไซต์การเรียกใช้เมธอดในระหว่างการดำเนินการ ระบบจะเรียกใช้ที่อยู่ในแคช (ในตัวสร้างโค้ดแบบไดนามิก การเรียกนี้เป็นการเรียกโดยตรง เนื่องจากที่อยู่โดยตรงจะถูกแก้ไขโดยตรรกะแคชพลาด) จากนั้นโค้ด Prologue ในเมธอดที่ถูกเรียกจะเปรียบเทียบคลาสที่แคชไว้กับคลาสของอ็อบเจ็กต์จริง และหากไม่ตรงกัน การดำเนินการจะแยกไปยังตัวจัดการแคชพลาดเพื่อค้นหาเมธอดที่ถูกต้องในคลาส การใช้งานที่รวดเร็วอาจมีรายการแคชหลายรายการ และมักใช้คำสั่งเพียงไม่กี่คำสั่งเพื่อให้การดำเนินการไปยังเมธอดที่ถูกต้องเมื่อแคชพลาดในครั้งแรก กรณีทั่วไปคือการจับคู่คลาสที่แคชไว้ และการดำเนินการจะดำเนินต่อไปในเมธอด
การแคชแบบนอกบรรทัดยังสามารถใช้ในตรรกะการเรียกใช้เมธอดได้ โดยใช้คลาสของอ็อบเจ็กต์และตัวเลือกเมธอด ในการออกแบบหนึ่ง คลาสและตัวเลือกเมธอดจะถูกแฮช และใช้เป็นดัชนีในตารางแคชการส่งเมธอด
เนื่องจาก Smalltalk เป็นภาษาแบบสะท้อน (reflective language) การใช้งานหลายๆ อย่างจึงอนุญาตให้เปลี่ยนแปลงวัตถุแต่ละชิ้นให้กลายเป็นวัตถุที่มีตารางค้นหาเมธอดที่สร้างขึ้นแบบไดนามิกได้ ซึ่งทำให้สามารถเปลี่ยนแปลงพฤติกรรมของวัตถุแต่ละชิ้นได้ ภาษาประเภทหนึ่งที่เรียกว่าภาษาแบบใช้ต้นแบบ (prototype-based languages)จึงเกิดขึ้นจากสิ่งนี้ ภาษาที่มีชื่อเสียงที่สุดคือSelfและJavaScriptการออกแบบแคชการเรียกใช้เมธอดอย่างรอบคอบช่วยให้แม้แต่ภาษาแบบใช้ต้นแบบก็สามารถเรียกใช้เมธอดได้อย่างมีประสิทธิภาพสูง
ภาษาโปรแกรมแบบไดนามิกอื่นๆ อีกมากมาย รวมถึงPython , Ruby , Objective-CและGroovyก็ใช้วิธีการที่คล้ายคลึงกัน
ตัวอย่าง
ซี
การเรียกใช้ฟังก์ชันแบบไดนามิกไม่ได้มีอยู่ใน ภาษา Cเหมือนกับภาษาอื่นๆ แต่ก็ยังสามารถทำได้โดยการจัดการตัวชี้ฟังก์ชันด้วยตนเอง
#include <stdio.h> #include <stdlib.h>typedef struct Pet { const char * name ; void ( * speak )( struct Pet * ); // เก็บตัวชี้ฟังก์ชัน} Pet ;Pet * createPet ( const char * name , void ( * speakFunc )( Pet * )) { Pet * pet = ( Pet * ) malloc ( sizeof ( Pet )); pet -> name = name ; pet -> speak = speakFunc ; return pet ; }void destroyPet ( Pet * pet ) { free ( pet ); }void dogSpeak ( Pet * pet ) { printf ( "%s พูดว่า 'เห่า!' \n " , pet -> name ); }void catSpeak ( Pet * pet ) { printf ( "%s พูดว่า 'เหมียว!' \n " , pet -> name ); }void speak ( Pet * pet ) { pet -> speak ( pet ); }int main () { Pet * fido = createPet ( "Fido" , dogSpeak ); Pet * simba = createPet ( "Simba" , catSpeak );speak ( fido ); // เรียกใช้ dogSpeak() speak ( simba ); // เรียกใช้ catSpeak();// ทำความสะอาด; ปลดปล่อยทรัพยากรdestroyPet ( fido ); destroyPet ( simba ); return 0 ; }ซี++
import std ;โดยใช้std :: string ;// กำหนดให้ Pet เป็นคลาสเสมือนนามธรรมclass Pet { protected : string name ; public : explicit Pet ( const string & name ) : name { name } {} }virtual void speak () = 0 ; };คลาสDog : public Pet { public : explicit Dog ( const string & name ) : Pet ( name ) {}void speak () override { std :: println ( "{} says 'Woof!'" , name ); } };คลาสCat : public Pet { public : explicit Cat ( const string & name ) : Pet ( name ) {}void speak () override { std :: println ( "{} says 'Meow!'" , name ); } };// speak() จะสามารถรับอะไรก็ได้ที่สืบทอดมาจาก Pet void speak ( Pet & pet ) { pet . speak (); }int main () { สุนัขfido ( "Fido" ); แมวsimba ( "Simba" ); พูด( fido ); พูด( simba ); return 0 ; }ซี#
เนมสเปซWikipedia.Examples ;โดยใช้ระบบ;คลาสแบบนามธรรมPet { protected string name ;public Pet ( string name ) { this . name = name ; }public abstract void Speak (); }คลาสDog : Pet { public Dog ( string name ) : base ( name ) { }public override void Speak () { Console.WriteLine ( $ "{name} says 'Woof!'" ) ; } }คลาสCat : Pet { public Cat ( string name ) : base ( name ) { }public override void Speak () { Console.WriteLine ( $ "{name} says 'Meow!'" ) ; } }public class Main { public static void Speak ( Pet pet ) { pet . Speak (); }public static void Main () { Dog fido = new ( "Fido" ); Cat simba = new ( "Simba" ); Speak ( fido ); Speak ( simba ); } }ชวา
แพ็คเกจorg.wikipedia.examples ;คลาสแบบนามธรรมPet { protected String name ;public Pet ( String name ) { this . name = name ; }public abstract void speak (); }คลาสDog สืบทอดมาจากPet { public Dog ( String name ) { super ( name ); }@Override public void speak () { System.out.printf ( " % s says ' Woof !'%n" , name ); } }คลาสCat สืบทอดมาจากPet { public Cat ( String name ) { super ( name ); }@Override public void speak () { System.out.printf ( " % s says ' Meow !'%n" , name ); } };public class Main { public static void speak ( Pet pet ) { pet . speak (); }public static void main ( String [] args ) { Dog fido = new Dog ( "Fido" ); Cat simba = new Cat ( "Simba" ); speak ( fido ); speak ( simba ); } }ไพธอน
จากabc นำเข้าABC , abstractmethod จากtyping นำเข้าNever# ABC เป็นคลาสที่ใช้เพื่อระบุว่าคลาสที่สืบทอดโดยตรงจากคลาสนี้เป็นคลาสแบบนามธรรมclass Pet ( ABC ): def __init__ ( self , name : str ) -> None : self . name = name@abstractmethod def speak ( self ) -> Never : raise NotImplementedError ( "เมธอดแบบนามธรรมต้องถูกใช้งานโดยคลาสที่สืบทอด" )คลาสDog ( Pet ): def __init__ ( self , name : str ) -> None : super . __init__ ( name )def speak ( self ) -> None : print ( f " { self.name } says 'Woof!' " )คลาสCat ( Pet ): def __init__ ( self , name : str ) -> None : super . __init__ ( name )def speak ( self ) - > None : print ( f " { self.name } พูดว่า 'เหมียว!' " )def speak ( pet : Pet ) -> None : # เรียกใช้เมธอด speak แบบไดนามิก# pet สามารถเป็นอินสแตนซ์ของ Dog หรือ Cat ก็ได้pet . speak ()ถ้า__name__ == "__main__" : fido : Dog = Dog ( "Fido" ) speak ( fido ) simba : Cat = Cat ( "Simba" ) speak ( simba )สนิม
ลักษณะสัตว์เลี้ยง{ fn speak ( & self ); }struct Dog <' a > { name : & ' a str }struct Cat <' a > { name : & ' a str }impl <' a > Dog <' a > { fn new ( name : & ' a str ) -> Self { Dog { name } } }impl <' a > Cat <' a > { fn new ( name : & ' a str ) -> Self { Cat { name } } }impl <' a > สัตว์เลี้ยงสำหรับสุนัข<' a > { fn speak ( & self ) { println! ( "{} พูดว่า 'เห่า!'" , self . name ); } }impl <' a > สัตว์เลี้ยงสำหรับแมว<' a > { fn speak ( & self ) { println! ( "{} พูดว่า 'เหมียว!'" , self . name ); } }// speak() ใช้ dynamic dispatch และแก้ไขประเภทในขณะรันไทม์// สำหรับประเภทใดๆ ที่ใช้งาน trait Pet fn speak ( pet : & dyn Pet ) { pet . speak (); }fn main () { let fido : Dog = Dog :: new ( "Fido" ); let simba : Cat = Cat :: new ( "Simba" ); speak ( & fido ); speak ( & simba ); }ดูเพิ่มเติม
อ่านเพิ่มเติม
- Lippman, Stanley B. (1996). Inside the C++ Object Model . Addison-Wesley . ISBN 0-201-83454-5.
- Groeber, Marcus; Di Geronimo, Jr., Edward "Ed"; Paul, Matthias R. (2002-03-02) [2002-02-24]. "ข้อมูล GEOS/NDO สำหรับ RBIL62?" . กลุ่มข่าว : comp.os.geos.programmer . สืบค้นเมื่อ2019-04-20 .
[…] เหตุผลที่
Geos
ต้องการอินเตอร์รัปต์ 16 ตัวก็เพราะว่ารูปแบบนี้ใช้ในการแปลงการเรียกฟังก์ชันระหว่างเซกเมนต์ ("far") ให้เป็นอินเตอร์รัปต์ โดยไม่เปลี่ยนแปลงขนาดของโค้ด เหตุผลที่ทำเช่นนี้ก็เพื่อให้ "บางสิ่ง" (เคอร์เนล) สามารถแทรกตัวเองเข้าไปในการเรียกฟังก์ชันระหว่างเซกเมนต์ทุกครั้งที่แอปพลิเคชัน Geos ทำ และตรวจสอบให้แน่ใจว่าเซกเมนต์โค้ดที่เหมาะสมถูกโหลดจาก
หน่วยความจำเสมือน
และล็อกไว้ ใน แง่
ของ DOS
สิ่งนี้จะเทียบได้กับ ตัวโหลด
โอเวอร์เลย์
แต่เป็นตัวที่สามารถเพิ่มได้โดยไม่ต้องอาศัยการสนับสนุนอย่างชัดเจนจากคอมไพเลอร์หรือแอปพลิเคชัน สิ่งที่เกิดขึ้นมีลักษณะดังนี้: […] 1. คอมไพเลอร์โหมดเรียลจะสร้างคำสั่งแบบนี้: CALL
<segment>:<offset>
-> 9A <offlow><offhigh><seglow><seghigh> โดยที่ <seglow><seghigh> ปกติจะถูกกำหนดให้เป็นแอดเดรสที่ต้องแก้ไขในเวลาโหลด ขึ้นอยู่กับแอดเดรสที่วางโค้ดไว้ […] 2. ตัวเชื่อมโยง Geos จะแปลงสิ่งนี้เป็นอย่างอื่น: INT 8xh -> CD 8x […] DB <seghigh>,<offlow>,<offhigh> […] โปรดสังเกตว่านี่ก็เป็นห้าไบต์เช่นกัน ดังนั้นจึงสามารถแก้ไขได้ "ในตำแหน่งเดิม" ปัญหาคือ อินเตอร์รัปต์ต้องการสองไบต์ ในขณะที่คำสั่ง CALL FAR ต้องการเพียงหนึ่งไบต์เท่านั้น ผลก็คือ เวกเตอร์ 32 บิต (<seg><ofs>) จะต้องถูกบีบอัดให้เหลือ 24 บิต […] การดำเนินการนี้ทำได้โดยสองสิ่ง: ประการแรก ที่อยู่ <seg> จะถูกเข้ารหัสเป็น "แฮนด์เดิล" สำหรับเซ็กเมนต์ ซึ่ง
นิบเบิล
ล่างสุด จะเป็นศูนย์เสมอ วิธีนี้ช่วยประหยัดได้สี่บิต นอกจากนี้ […] บิตที่เหลืออีกสี่บิตจะถูกใส่เข้าไปในนิบเบิลล่างของเวกเตอร์อินเตอร์รัปต์ ทำให้เกิดค่าใดๆ ก็ได้ตั้งแต่ INT 80h ถึง 8Fh […] ตัวจัดการอินเตอร์รัปต์สำหรับเวกเตอร์ทั้งหมดเหล่านั้นเหมือนกัน มันจะ "แยก" ที่อยู่จากสัญกรณ์สามไบต์ครึ่ง ค้นหาที่อยู่สัมบูรณ์ของเซ็กเมนต์ และส่งต่อการเรียกหลังจากที่ได้ทำการโหลดหน่วยความจำเสมือนแล้ว... การกลับจากการเรียกจะผ่านรหัสปลดล็อกที่เกี่ยวข้องด้วย […] นิบเบิลล่างของเวกเตอร์อินเตอร์รัปต์ (80h–8Fh) เก็บค่าบิตที่ 4 ถึง 7 ของแฮนด์เดิลเซ็กเมนต์ บิตที่ 0 ถึง 3 ของแฮนเดิลเซ็กเมนต์ (ตามนิยามของแฮนเดิล Geos) จะเป็น 0 เสมอ […] API ของ Geos ทั้งหมดทำงานผ่านรูปแบบ "โอเวอร์เลย์" […]: เมื่อแอปพลิเคชัน Geos ถูกโหลดเข้าสู่หน่วยความจำ ตัวโหลดจะแทนที่การเรียกใช้ฟังก์ชันในไลบรารีของระบบด้วยการเรียกใช้แบบ INT ที่สอดคล้องกันโดยอัตโนมัติ อย่างไรก็ตาม ค่าเหล่านี้ไม่คงที่ แต่ขึ้นอยู่กับแฮนเดิลที่กำหนดให้กับเซ็กเมนต์โค้ดของไลบรารี[…] เดิมที Geos ตั้งใจที่จะแปลงเป็น
โหมดป้องกัน
ตั้งแต่แรกเริ่ม […] โดยใช้
โหมดจริง
เนื่องจากเป็นเพียง "ตัวเลือกแบบเดิม" […] โค้ดแอสเซมบลีเกือบทุกบรรทัดพร้อมใช้งานแล้ว […]
{{cite newsgroup}}: CS1 maint: บริการเก็บถาวรที่เลิกใช้แล้ว ( ลิงก์ ) - Paul, Matthias R. (2002-04-11). "Re: [ fd-dev ]ประกาศ: CuteMouse 2.0 alpha 1" . freedos-dev . เก็บถาวรจากต้นฉบับเมื่อ 2020-02-21 . เรียกดูเมื่อ2020-02-21 .
[…] ในกรณีของพอยเตอร์ที่บิดเบี้ยวเช่นนี้ […] หลายปีก่อน Axel และผมกำลังคิดหาวิธีที่จะใช้จุดเริ่มต้น *หนึ่ง* จุดในไดรเวอร์สำหรับเวกเตอร์การขัดจังหวะหลายตัว (เนื่องจากจะช่วยประหยัดพื้นที่สำหรับจุดเริ่มต้นหลายจุดและโค้ดเฟรมการเริ่มต้น/ออกที่เหมือนกันในทุกจุด) จากนั้นจึงสลับไปยังตัวจัดการการขัดจังหวะที่แตกต่างกันภายใน ตัวอย่างเช่น: 1234h:0000h […] 1233h:0010h […] 1232h:0020h […] 1231h:0030h […] 1230h:0040h […] ทั้งหมดชี้ไปยังจุดเริ่มต้นเดียวกัน หากคุณเชื่อมต่อ INT 21h กับ 1234h:0000h และ INT 2Fh กับ 1233h:0010h และอื่นๆ พวกมันทั้งหมดจะผ่าน "ช่องโหว่" เดียวกัน แต่คุณยังคงสามารถแยกแยะความแตกต่างระหว่างพวกมันและแยกไปยังตัวจัดการต่างๆ ภายในได้ ลองนึกถึงจุดเริ่มต้นที่ "บีบอัด" ลงในA20 stub สำหรับ การโหลด HMAวิธีนี้ใช้ได้ตราบใดที่ไม่มีโปรแกรมใดเริ่มทำเวทมนตร์ segment:offset […] เปรียบเทียบกับวิธีการตรงกันข้ามในการมีจุดเริ่มต้นหลายจุด (อาจรองรับโปรโตคอลการแบ่งปันการขัดจังหวะของIBM ด้วยซ้ำ ) ซึ่งจะใช้หน่วยความจำมากกว่ามากหากคุณเชื่อมต่อการขัดจังหวะจำนวนมาก […] เราได้ข้อสรุปว่า วิธีนี้อาจไม่ปลอดภัยในทางปฏิบัติ เพราะคุณไม่มีทางรู้ได้เลยว่าไดรเวอร์อื่นๆ จะปรับค่าตัวชี้ให้เป็นมาตรฐานหรือไม่เป็นมาตรฐานด้วยเหตุผลใดก็ตาม […]
(หมายเหตุ: คล้ายกับ " fat pointers " โดยเฉพาะสำหรับ การกำหนด แอดเดรสแบบsegment:offset ในโหมดเรียลของIntelบน โปรเซสเซอร์ x86ซึ่งประกอบด้วยพอยเตอร์ที่ถูกทำให้ไม่ปกติโดยเจตนาไปยังจุดเริ่มต้นของโค้ดที่ใช้ร่วมกัน และข้อมูลบางอย่างเพื่อแยกแยะผู้เรียกใช้ที่แตกต่างกันในโค้ดที่ใช้ร่วมกัน ในขณะที่ในระบบเปิด การทำให้พอยเตอร์เป็นมาตรฐานโดยอินสแตนซ์ของบุคคลที่สาม (ในไดรเวอร์หรือแอปพลิเคชันอื่น ๆ) ไม่สามารถตัดออกไปได้อย่างสมบูรณ์บนอินเทอร์เฟซสาธารณะแต่รูปแบบนี้สามารถใช้ได้อย่างปลอดภัยบนอินเทอร์เฟซภายในเพื่อหลีกเลี่ยงลำดับโค้ดเริ่มต้นที่ซ้ำซ้อน) - ไบรท์, วอลเตอร์ (2009-12-22). "ความผิดพลาดครั้งใหญ่ที่สุดของซี" . ดิจิทัล มาร์ส . เก็บถาวรจากต้นฉบับเมื่อ 2022-06-08 . เรียกดูเมื่อ2022-07-11 .
- โฮลเดน, แดเนียล (2015). "คลังพอยน์เตอร์อ้วน" . เชลโล: ระดับสูง C . เก็บถาวรจากต้นฉบับเมื่อ 2022-07-11 . เรียกดูเมื่อ2022-07-11 .