อ่าน 12 นาที
การบิดเบือนชื่อ
ในการสร้างคอม ไพเลอร์ การแปลงชื่อ (หรือเรียกว่า การตกแต่งชื่อ ) เป็นเทคนิคที่ใช้เพื่อแก้ปัญหาต่างๆ ที่เกิดจากความจำเป็นในการกำหนดชื่อที่ไม่ซ้ำกันสำหรับเอนทิตีการเขียนโปรแกรมใน...
การบิดเบือนชื่อ
ในการสร้างคอมไพเลอร์การแปลงชื่อ (หรือเรียกว่าการตกแต่งชื่อ ) เป็นเทคนิคที่ใช้เพื่อแก้ปัญหาต่างๆ ที่เกิดจากความจำเป็นในการกำหนดชื่อที่ไม่ซ้ำกันสำหรับเอนทิตีการเขียนโปรแกรมในภาษาโปรแกรม สมัยใหม่หลาย ภาษา
มันเป็นกลไกในการเข้ารหัสข้อมูลเพิ่มเติมในชื่อของฟังก์ชันโครงสร้างคลาสหรือชนิดข้อมูลอื่นเพื่อส่งข้อมูลเชิงความหมายเพิ่มเติมจากคอมไพเลอร์ไปยังลิง เกอร์
ความจำเป็นในการแปลงชื่อเกิดขึ้นเมื่อภาษาโปรแกรมอนุญาตให้ใช้ชื่อเดียวกันกับเอนทิตี ที่แตกต่างกันได้ ตราบใดที่เอนทิตีเหล่านั้นอยู่ในเนมสเปซ ที่แตกต่างกัน (โดยทั่วไปกำหนดโดยโมดูล คลาส หรือ คำสั่ง เนมสเปซ ที่ระบุอย่างชัดเจน ) หรือมีรูปแบบการเรียกใช้ฟังก์ชัน ที่แตกต่างกัน (เช่น ในการโอเวอร์โหลดฟังก์ชัน ) จำเป็นต้องใช้การแปลงชื่อในกรณีเหล่านี้ เนื่องจากแต่ละรูปแบบการเรียกใช้ฟังก์ชันอาจต้องการข้อกำหนดการเรียกใช้ เฉพาะที่แตกต่างกัน ในโค้ดเครื่อง
โดยปกติแล้ว โค้ดวัตถุที่สร้างโดยคอมไพเลอร์จะถูกเชื่อมโยงกับโค้ดวัตถุอื่นๆ (ที่สร้างโดยคอมไพเลอร์เดียวกันหรือคอมไพเลอร์อื่น) โดยโปรแกรมประเภทหนึ่งที่เรียกว่าตัวเชื่อมโยง ( linker) ตัวเชื่อมโยงต้องการข้อมูลจำนวนมากเกี่ยวกับแต่ละส่วนประกอบของโปรแกรม ตัวอย่างเช่น ในการเชื่อมโยงฟังก์ชันอย่างถูกต้อง ตัวเชื่อมโยงต้องการชื่อฟังก์ชัน จำนวนอาร์กิวเมนต์และชนิดของอาร์กิวเมนต์ และอื่นๆ อีกมากมาย
ภาษาโปรแกรมแบบง่ายๆ ในช่วงทศวรรษ 1970 เช่นภาษา Cนั้น แยกแยะซับรูทีนโดยใช้เพียงชื่อเท่านั้น โดยไม่สนใจข้อมูลอื่นๆ รวมถึง ชนิด ของพารามิเตอร์และค่าที่ส่งคืน ภาษาในยุคต่อมา เช่น ภาษาC++ได้กำหนดข้อกำหนดที่เข้มงวดมากขึ้นสำหรับรูทีนที่จะถือว่า "เท่าเทียมกัน" เช่น ชนิดของพารามิเตอร์ ชนิดของค่าที่ส่งคืน และข้อกำหนดในการเรียกใช้ฟังก์ชัน ข้อกำหนดเหล่านี้ทำให้สามารถโอเวอร์โหลดเมธอดและตรวจจับข้อผิดพลาดบางอย่างได้ (เช่น การใช้คำจำกัดความที่แตกต่างกันของฟังก์ชันเมื่อคอมไพล์ ไฟล์ ซอร์สโค้ด ที่แตกต่างกัน ) ข้อกำหนดที่เข้มงวดเหล่านี้จำเป็นต้องทำงานร่วมกับเครื่องมือและข้อกำหนดการเขียนโปรแกรมที่มีอยู่ ดังนั้น ข้อกำหนดเพิ่มเติมจึงถูกเข้ารหัสไว้ในชื่อของสัญลักษณ์ เนื่องจากนั่นเป็นข้อมูลเดียวที่ลิงเกอร์แบบดั้งเดิมมีเกี่ยวกับสัญลักษณ์นั้น
ตัวอย่าง
ซี
แม้ว่าการแปลงชื่อฟังก์ชัน (name mangling) โดยทั่วไปจะไม่จำเป็นหรือไม่ได้ใช้ในภาษาโปรแกรมที่ไม่รองรับการโอเวอร์โหลดฟังก์ชันเช่นภาษา Cและภาษา Pascal แบบคลาสสิก แต่ภาษาเหล่านี้ก็ใช้ในบางกรณีเพื่อให้ข้อมูลเพิ่มเติมเกี่ยวกับฟังก์ชัน ตัวอย่างเช่น คอมไพเลอร์ที่มุ่งเป้าไปที่ แพลตฟอร์ม Microsoft Windows รองรับ ข้อกำหนดการเรียกใช้ฟังก์ชันที่หลากหลายซึ่งกำหนดวิธีการส่งพารามิเตอร์ไปยังซับรูทีนและส่งคืนผลลัพธ์ เนื่องจากข้อกำหนดการเรียกใช้ฟังก์ชันที่แตกต่างกันนั้นไม่เข้ากัน คอมไพเลอร์จึงแปลงสัญลักษณ์ด้วยรหัสที่ระบุรายละเอียดว่าควรใช้ข้อกำหนดใดในการเรียกใช้รูทีนเฉพาะนั้น
รูปแบบการแมงลิง (Mangler scheme) สำหรับ Windows นั้นถูกกำหนดขึ้นโดย Microsoft และถูกนำไปใช้โดยไม่เป็นทางการโดยคอมไพเลอร์อื่นๆ เช่นDigital Mars , BorlandและGNU Compiler Collection (GCC) เมื่อคอมไพล์โค้ดสำหรับแพลตฟอร์ม Windows รูปแบบนี้ยังใช้ได้กับภาษาอื่นๆ เช่นPascal , D , Delphi , FortranและC# ด้วย ซึ่งช่วยให้ซับรูทีนที่เขียนด้วยภาษาเหล่านั้นสามารถเรียกใช้ หรือถูกเรียกใช้โดย ไลบรารีของ Windows ที่มีอยู่แล้วโดยใช้รูปแบบการเรียกใช้ที่แตกต่างจากค่าเริ่มต้น
เมื่อคอมไพล์ตัวอย่างภาษา C ต่อไปนี้:
int _cdecl f ( int x ) { return 0 ; }int _stdcall g ( int y ) { return 0 ; }int _fastcall h ( int z ) { return 0 ; }คอมไพเลอร์ 32 บิตจะสร้างผลลัพธ์ดังนี้:
_f _g@4 @h@4
ใน รูปแบบการเข้ารหัสแบบ stdcall`and` และfastcall`mangling` ฟังก์ชันจะถูกเข้ารหัสเป็น ` and` และ `or` ตามลำดับ โดยที่Xคือจำนวนไบต์ในเลขฐานสิบของอาร์กิวเมนต์ในรายการพารามิเตอร์ (รวมถึงอาร์กิวเมนต์ที่ส่งผ่านในรีจิสเตอร์สำหรับ fastcall) ในกรณีของ `or` ชื่อฟังก์ชันจะถูกนำหน้าด้วยเครื่องหมายขีดล่างเท่านั้น _name@X@name@Xcdecl
ตามธรรมเนียมการเขียนโค้ด 64 บิตบน Windows (Microsoft C) จะไม่มีเครื่องหมายขีดล่างนำหน้า ความแตกต่างนี้อาจทำให้เกิดปัญหาการเชื่อมต่อภายนอกที่ไม่สามารถแก้ไขได้ในบางกรณีเมื่อทำการแปลงโค้ดดังกล่าวเป็น 64 บิต ตัวอย่างเช่น โค้ด Fortran สามารถใช้ 'alias' เพื่อเชื่อมโยงกับเมธอด C โดยใช้ชื่อดังนี้:
SUBROUTINE f () !DEC$ ATTRIBUTES C, ALIAS:'_f' :: f END SUBROUTINEโค้ดนี้จะคอมไพล์และลิงก์ได้ดีในระบบ 32 บิต แต่จะสร้างข้อผิดพลาด "unresolved external" _fในระบบ 64 บิต วิธีแก้ปัญหาอย่างหนึ่งคือ ไม่ใช้ 'alias' เลย (ซึ่งชื่อเมธอดมักจะต้องขึ้นต้นด้วยตัวพิมพ์ใหญ่ในภาษา C และ Fortran) อีกวิธีหนึ่งคือ ใช้BINDตัวเลือก Fortran 2003:
SUBROUTINE f () BIND ( C , NAME = "f" ) END SUBROUTINEในภาษา C คอมไพเลอร์ส่วนใหญ่จะทำการแปลงชื่อฟังก์ชันและตัวแปรแบบ static (และใน C++ ฟังก์ชันและตัวแปรที่ประกาศเป็น static หรืออยู่ใน namespace แบบไม่ระบุชื่อ) ในหน่วยการแปลโดยใช้กฎการแปลงชื่อเดียวกันกับเวอร์ชันที่ไม่ใช่ static หากฟังก์ชันที่มีชื่อเดียวกัน (และพารามิเตอร์สำหรับ C++) ถูกนิยามและใช้งานในหน่วยการแปลที่แตกต่างกัน ฟังก์ชันเหล่านั้นก็จะถูกแปลงชื่อเป็นชื่อเดียวกัน ซึ่งอาจนำไปสู่ความขัดแย้งได้ อย่างไรก็ตาม ฟังก์ชันเหล่านั้นจะไม่เทียบเท่ากันหากถูกเรียกใช้ในหน่วยการแปลที่เกี่ยวข้อง คอมไพเลอร์มักจะมีอิสระในการกำหนดการแปลงชื่อฟังก์ชันเหล่านี้อย่างอิสระ เนื่องจากเป็นการผิดกฎหมายที่จะเข้าถึงฟังก์ชันเหล่านี้จากหน่วยการแปลอื่นโดยตรง ดังนั้นจึงไม่จำเป็นต้องมีการเชื่อมโยงระหว่างโค้ดออบเจ็กต์ที่แตกต่างกัน (การเชื่อมโยงฟังก์ชันเหล่านี้ไม่จำเป็นเลย) เพื่อป้องกันความขัดแย้งในการเชื่อมโยง คอมไพเลอร์จะใช้การแปลงชื่อมาตรฐาน แต่จะใช้สัญลักษณ์ที่เรียกว่า 'สัญลักษณ์เฉพาะที่' เมื่อเชื่อมโยงหน่วยการแปลจำนวนมาก อาจมีคำนิยามของฟังก์ชันที่มีชื่อเดียวกันหลายรายการ แต่โค้ดที่ได้จะเรียกใช้ฟังก์ชันใดฟังก์ชันหนึ่งเท่านั้น ขึ้นอยู่กับว่ามาจากหน่วยการแปลใด โดยปกติแล้วจะทำโดยใช้กลไก การย้ายตำแหน่ง
ซี++
คอมไพเลอร์ C++เป็นผู้ใช้งานการแปลงชื่อสัญลักษณ์ (name mangling) ที่แพร่หลายที่สุด คอมไพเลอร์ C++ รุ่นแรกๆ ถูกพัฒนาขึ้นเพื่อเป็นตัวแปลง โค้ดต้นฉบับ Cซึ่งจะถูกคอมไพล์โดยคอมไพเลอร์ C อีกครั้งให้เป็นโค้ดออบเจ็กต์ ด้วยเหตุนี้ ชื่อสัญลักษณ์จึงต้องเป็นไปตามกฎการระบุตัวตนของ C แม้แต่ในภายหลัง เมื่อมีคอมไพเลอร์ที่สร้างโค้ดเครื่องหรือโค้ดแอสเซมบลีโดยตรง ระบบลิงเกอร์โดยทั่วไปก็ยังไม่รองรับสัญลักษณ์ C++ และการแปลงชื่อสัญลักษณ์ก็ยังคงจำเป็นอยู่
ภาษาC++ไม่ได้กำหนดรูปแบบการตกแต่งมาตรฐาน ดังนั้นคอมไพเลอร์แต่ละตัวจึงใช้รูปแบบของตนเอง นอกจากนี้ C++ ยังมีคุณลักษณะทางภาษาที่ซับซ้อน เช่นคลาสเทมเพลตเนมสเปซและการโอเวอร์โหลดตัวดำเนินการซึ่งเปลี่ยนแปลงความหมายของสัญลักษณ์เฉพาะตามบริบทหรือการใช้งาน ข้อมูลเมตาเกี่ยวกับคุณลักษณะเหล่านี้สามารถแยกแยะได้โดยการเปลี่ยนแปลง (ตกแต่ง) ชื่อของสัญลักษณ์เนื่องจากระบบการเปลี่ยนแปลงชื่อสำหรับคุณลักษณะดังกล่าวไม่ได้เป็นมาตรฐานในคอมไพเลอร์ต่างๆ จึงมีลิงเกอร์เพียงไม่กี่ตัวที่สามารถเชื่อมโยงโค้ดออบเจ็กต์ที่สร้างโดยคอมไพเลอร์ที่แตกต่างกันได้
ตัวอย่างง่ายๆ
หน่วยการแปลภาษา C++ หนึ่งหน่วยอาจกำหนดฟังก์ชันสองฟังก์ชันที่มีชื่อว่าf():
int f () { return 1 ; }int f ([[ maybe_unused ]] int x ) { return 0 ; }void g () { int i = f (); int j = f ( 0 ); }ฟังก์ชันเหล่านี้แตกต่างกันโดยสิ้นเชิง ไม่มีความสัมพันธ์กันนอกจากชื่อ ดังนั้นคอมไพเลอร์ C++ จะเข้ารหัสข้อมูลประเภทลงในชื่อสัญลักษณ์ ผลลัพธ์ที่ได้จึงมีลักษณะคล้ายกับ:
int __f_v () { return 1 ; }int __f_i ([[ maybe_unused ]] int x ) { return 0 ; } void __g_v () { int i = __f_v (); int j = __f_i ( 0 ); }ถึงแม้ชื่อจะไม่ซ้ำใคร แต่g()ก็ยังถูกบิดเบือนอยู่ดี: การบิดเบือนชื่อนี้ใช้กับ สัญลักษณ์ C++ ทั้งหมด (ยกเว้นสัญลักษณ์ที่อยู่ในบล็อก) extern"C"
ตัวอย่างที่ซับซ้อน
สัญลักษณ์ที่ผิดเพี้ยนในตัวอย่างนี้ ซึ่งแสดงอยู่ในส่วนความคิดเห็นใต้ชื่อตัวระบุที่เกี่ยวข้อง คือสัญลักษณ์ที่สร้างขึ้นโดยคอมไพเลอร์ GNU GCC 3.x ตามมาตรฐานIA-64 (Itanium) ABI:
ส่งออกโมดูลวิกิพีเดียโครงสร้างบทความ;import std ;using std :: ostream ; using std :: string ; using std :: string_view ;ส่งออกเนมสเปซวิกิพีเดีย:: โครงสร้าง{คลาสArticle { public : explicit Article ( string_view name );~ บทความ() = ค่าเริ่มต้น;[[ nodiscard ]] รูปแบบสตริง(); // = _ZN9wikipedia8structure7Article6formatEvbool printTo ( ostream & os ); // = _ZN9wikipedia8structure7article6printToERSoคลาสWikilink { public : explicit Wikilink ( string_view name ); // = _ZN9wikipedia8structure7Article6WikilinkC1ERKSs~ Wikilink () = default ; }; };}สัญลักษณ์ที่ถูกดัดแปลงทั้งหมดเริ่มต้นด้วย_Z(โปรดทราบว่าตัวระบุที่ขึ้นต้นด้วยเครื่องหมายขีดล่างตามด้วยตัวอักษรตัวใหญ่เป็นตัวระบุที่สงวนไว้ในภาษา C ดังนั้นจึงหลีกเลี่ยงความขัดแย้งกับตัวระบุของผู้ใช้) สำหรับชื่อที่ซ้อนกัน (รวมทั้งเนมสเปซและคลาส) จะตามด้วย จากนั้นตามด้วยNคู่ <ความยาว, รหัส> หลายๆ คู่ (ความยาวคือความยาวของตัวระบุถัดไป) และสุดท้ายคือEตัวอย่างเช่นwikipedia::structure::Article::format()จะกลายเป็น:
_ZN9wikipedia8structure7Article6formatE
สำหรับฟังก์ชัน จะตามด้วยข้อมูลประเภท เนื่องจากformat()ฟังก์ชันไม่รับอาร์กิวเมนต์ใดๆ ซึ่งเทียบเท่ากับformat(void)ดังนั้นจึงเป็นเพียงv; ดังนั้น:
_ZN9wikipedia8structure7Article6formatEv
สำหรับจะใช้wikipedia::structure::Article::printTo()ประเภทมาตรฐานstd::ostream(ซึ่งเป็นtypedefสำหรับ ) ซึ่งมีชื่อเรียกเฉพาะว่า ; ดังนั้นการอ้างอิงถึงประเภทนี้จึงเป็นโดยชื่อเต็มของฟังก์ชันคือ: std::basic_ostream<char, std::char_traits<char>>SoRSo
_ZN9วิกิพีเดีย8โครงสร้าง7บทความ6พิมพ์ถึงดังนั้น
โปรดทราบว่าใน Itanium ABI ประเภทการส่งคืนจะถูกละเว้นสำหรับฟังก์ชันที่ไม่ใช่เทมเพลต แต่จะถูกเข้ารหัสสำหรับฟังก์ชันเทมเพลต [ 1 ]
วิธีที่คอมไพเลอร์ต่างกันบิดเบือนฟังก์ชันเดียวกัน
ไม่มีรูปแบบมาตรฐานใด ๆ ที่ใช้ในการเปลี่ยนแปลงรหัสของตัวระบุในภาษา C++ แม้แต่ในระดับพื้นฐาน ดังนั้นคอมไพเลอร์ที่แตกต่างกัน (หรือแม้แต่เวอร์ชันที่แตกต่างกันของคอมไพเลอร์เดียวกัน หรือคอมไพเลอร์เดียวกันบนแพลตฟอร์มที่แตกต่างกัน) จึงเปลี่ยนแปลงรหัสของสัญลักษณ์สาธารณะในลักษณะที่แตกต่างกันอย่างสิ้นเชิง (และเข้ากันไม่ได้โดยสิ้นเชิง) ลองพิจารณาว่าคอมไพเลอร์ C++ ที่แตกต่างกันเปลี่ยนแปลงรหัสของฟังก์ชันเดียวกันอย่างไร:
| คอมไพเลอร์ | void h(int) | void h(int, char) | void h(void) |
|---|---|---|---|
| Intel C++ 8.0 สำหรับ Linux | _Z1hi | _Z1hic | _Z1hv |
| HP aC++ A.05.55 IA-64 | |||
| IAR EWARM C++ | |||
| GCC 3.x และสูงกว่า | |||
| Clang 1. xและสูงกว่า[ 2 ] | |||
| GCC 2.9. x | h__Fi | h__Fic | h__Fv |
| HP aC++ A.03.45 PA-RISC | |||
| Microsoft Visual C++เวอร์ชัน 6-10 ( รายละเอียดการเปลี่ยนแปลง ) | ?h@@YAXH@Z | ?h@@YAXHD@Z | ?h@@YAXXZ |
| ดิจิทัลมาร์ส ซี++ | |||
| บอร์แลนด์ซี++ เวอร์ชัน 3.1 | @h$qi | @h$qizc | @h$qv |
| OpenVMS C++ เวอร์ชัน 6.5 (โหมด ARM) | H__XI | H__XIC | H__XV |
| OpenVMS C++ เวอร์ชัน 6.5 (โหมด ANSI) | CXX$__7H__FIC26CDH77 | CXX$__7H__FV2CB06E8 | |
| OpenVMS C++ X7.1 IA-64 | CXX$_Z1HI2DSQ26A | CXX$_Z1HIC2NP3LI4 | CXX$_Z1HV0BCA19V |
| ซันโปร ซีซี | __1cBh6Fi_v_ | __1cBh6Fic_v_ | __1cBh6F_v_ |
| Tru64 C++ v6.5 (โหมด ARM) | h__Xi | h__Xic | h__Xv |
| Tru64 C++ v6.5 (โหมด ANSI) | __7h__Fi | __7h__Fic | __7h__Fv |
| Watcom C++ 10.6 | W?h$n(i)v | W?h$n(ia)v | W?h$n()v |
หมายเหตุ:
- คอมไพเลอร์ C++ ของ CompaqบนOpenVMS VAX และ Alpha (แต่ไม่ใช่ IA-64) และTru64 UNIXมีวิธีการแปลงชื่อสองแบบ วิธีดั้งเดิมก่อนมาตรฐานเรียกว่าแบบจำลอง ARM ซึ่งอิงตามการแปลงชื่อที่อธิบายไว้ในคู่มืออ้างอิง C++ Annotated Reference Manual (ARM) ด้วยการเกิดขึ้นของคุณสมบัติใหม่ๆ ใน C++ มาตรฐาน โดยเฉพาะอย่างยิ่งเทมเพลตวิธีการ ARM จึงไม่เหมาะสมมากขึ้นเรื่อยๆ – มันไม่สามารถเข้ารหัสประเภทฟังก์ชันบางประเภทได้ หรือสร้างชื่อที่แปลงแล้วเหมือนกันสำหรับฟังก์ชันที่แตกต่างกัน ดังนั้นจึงถูกแทนที่ด้วย แบบจำลอง American National Standards Institute (ANSI) ที่ใหม่กว่า ซึ่งรองรับคุณสมบัติเทมเพลต ANSI ทั้งหมด แต่ไม่สามารถใช้งานร่วมกับเวอร์ชันก่อนหน้าได้
- บนสถาปัตยกรรม IA-64 มีอินเทอร์เฟซไบนารีแอปพลิเคชัน มาตรฐาน (ABI) อยู่ (ดูลิงก์ภายนอก ) ซึ่งกำหนด (รวมถึงสิ่งอื่นๆ) รูปแบบการแปลงชื่อมาตรฐาน และถูกใช้โดยคอมไพเลอร์ IA-64 ทั้งหมด GNU GCC 3.x ได้นำรูปแบบการแปลงชื่อที่กำหนดไว้ในมาตรฐานนี้มาใช้กับแพลตฟอร์มอื่นๆ ที่ไม่ใช่ของ Intel ด้วย
- Visual Studioและ Windows SDK มีโปรแกรม
undnameที่พิมพ์ต้นแบบฟังก์ชันแบบ C สำหรับชื่อที่ถูกแปลงแล้วที่กำหนดให้ - บน Microsoft Windows คอมไพเลอร์ Intel [ 3 ]และClang [ 4 ]ใช้การแปลงชื่อ Visual C++ เพื่อความเข้ากันได้
การจัดการสัญลักษณ์ C เมื่อเชื่อมโยงจาก C++
หน้าที่ของสำนวนภาษา C++ ทั่วไป:
#ifdef __cplusplus extern "C" { #endif// สัญลักษณ์ตรงนี้#ifdef __cplusplus } #endifคือการทำให้แน่ใจว่าสัญลักษณ์ภายในนั้น "ไม่ถูกบิดเบือน" – กล่าวคือ คอมไพเลอร์จะสร้างไฟล์ไบนารีโดยที่ชื่อของสัญลักษณ์เหล่านั้นไม่มีการตกแต่งเพิ่มเติม เหมือนกับที่คอมไพเลอร์ภาษา C ทำ เนื่องจากคำจำกัดความของภาษา C นั้นไม่ถูกบิดเบือน คอมไพเลอร์ C++ จึงจำเป็นต้องหลีกเลี่ยงการบิดเบือนการอ้างอิงถึงตัวระบุเหล่านี้
ตัวอย่างเช่น ส่วนหัว<string.h>มักมีเนื้อหาคล้ายกับ:
#ifdef __cplusplus extern "C" { #endifvoid * memset ( void * s , int c , size_t n ); char * strcat ( char * s1 , const char * s2 ); int strcmp ( const char * s1 , const char * s2 ); char * strcpy ( char * s1 , const char * s2 );#ifdef __cplusplus } #endifดังนั้น โค้ดจึงมีลักษณะดังนี้:
ถ้า( strcmp ( argv [ 1 ], "-x" ) == 0 ) { strcpy ( a , argv [ 2 ]); } มิเช่นนั้น{ memset ( a , 0 , sizeof ( a )); }ใช้รูปแบบที่ถูกต้อง ไม่บิดเบือนstrcmpและmemset. หากextern "C"ไม่ได้ใช้ รูปแบบดังกล่าว คอมไพเลอร์ C++ (SunPro) จะสร้างโค้ดที่เทียบเท่ากับ:
ถ้า( __1cGstrcmp6Fpkc1_i_ ( argv [ 1 ], "-x" ) == 0 ) { __1cGstrcpy6Fpcpkc_0_ ( a , argv [ 2 ]); } มิฉะนั้น{ __1cGmemset6FpviI_0_ ( a , 0 , sizeof ( a )); }เนื่องจากสัญลักษณ์เหล่านั้นไม่มีอยู่ในไลบรารีรันไทม์ของภาษาซี ( เช่น libc) จึงจะทำให้เกิดข้อผิดพลาดในการเชื่อมโยง
การแปลงชื่อมาตรฐานใน C++
ดูเหมือนว่าการกำหนดมาตรฐานการแปลงชื่อในภาษา C++ จะนำไปสู่ความสามารถในการทำงานร่วมกันได้มากขึ้นระหว่างการใช้งานคอมไพเลอร์ต่างๆ อย่างไรก็ตาม การกำหนดมาตรฐานดังกล่าวเพียงอย่างเดียวไม่เพียงพอที่จะรับประกันความสามารถในการทำงานร่วมกันของคอมไพเลอร์ C++ และอาจสร้างความเข้าใจผิดว่าความสามารถในการทำงานร่วมกันเป็นไปได้และปลอดภัย ทั้งๆ ที่ไม่ใช่ การแปลงชื่อเป็นเพียงรายละเอียดหนึ่งในหลายๆ ส่วนของApplication Binary Interface (ABI) ที่การใช้งาน C++ ต้องตัดสินใจและปฏิบัติตาม ด้านอื่นๆ ของ ABI เช่นการจัดการข้อยกเว้น การจัดวาง ตารางเสมือนโครงสร้าง และการเติม ช่องว่างในเฟรมสแต็ก ก็ทำให้การใช้งาน C++ ที่แตกต่างกันไม่เข้ากันได้ นอกจากนี้ การกำหนดรูปแบบการแปลงชื่อเฉพาะจะทำให้เกิดปัญหาสำหรับระบบที่มีข้อจำกัดในการใช้งาน (เช่น ความยาวของสัญลักษณ์) ที่กำหนดรูปแบบการแปลงชื่อเฉพาะการกำหนด มาตรฐาน สำหรับการแปลงชื่อจะป้องกันการใช้งานที่ไม่ต้องการการแปลงชื่อเลย เช่น ตัวเชื่อมโยงที่เข้าใจภาษา C++
ดังนั้น มาตรฐานC++จึงไม่ได้พยายามกำหนดมาตรฐานการเปลี่ยนชื่อตัวแปร ในทางตรงกันข้ามคู่มืออ้างอิง C++ ฉบับมีคำอธิบายประกอบ (หรือที่รู้จักกันในชื่อARM , ISBN) กลับกำหนดมาตรฐานดังกล่าวไว้ 0-201-51459-1มาตรา 7.2.1c) สนับสนุนอย่างยิ่งให้ใช้รูปแบบการจัดการข้อมูลที่แตกต่างกันเพื่อป้องกันการเชื่อมโยงเมื่อลักษณะอื่นๆ ของ ABI ไม่เข้ากัน
อย่างไรก็ตาม ดังที่ได้อธิบายไว้ในส่วนข้างต้น ในบางแพลตฟอร์ม[ 5 ] ABI ของ C++ เต็มรูปแบบได้รับการกำหนดมาตรฐานแล้ว รวมถึงการเปลี่ยนชื่อด้วย
ผลกระทบในโลกแห่งความเป็นจริงของการแปลงชื่อโดเมนในภาษา C++
เนื่องจากสัญลักษณ์ C++ มักถูกส่งออกจาก ไฟล์ DLLและ ไฟล์ออบ เจ็กต์ที่ใช้ร่วมกันดังนั้นรูปแบบการตั้งชื่อจึงไม่ใช่เรื่องภายในของคอมไพเลอร์เท่านั้น คอมไพเลอร์ที่แตกต่างกัน (หรือเวอร์ชันที่แตกต่างกันของคอมไพเลอร์เดียวกัน ในหลายกรณี) สร้างไบนารีดังกล่าวภายใต้รูปแบบการตกแต่งชื่อที่แตกต่างกัน ซึ่งหมายความว่าสัญลักษณ์มักจะไม่สามารถแก้ไขได้หากคอมไพเลอร์ที่ใช้สร้างไลบรารีและโปรแกรมที่ใช้ไลบรารีนั้นใช้รูปแบบที่แตกต่างกัน ตัวอย่างเช่น หากระบบที่มีคอมไพเลอร์ C++ หลายตัวติดตั้งอยู่ (เช่น GNU GCC และคอมไพเลอร์ของผู้จำหน่ายระบบปฏิบัติการ) ต้องการติดตั้งไลบรารี Boost C++ก็จะต้องคอมไพล์หลายครั้ง (ครั้งหนึ่งสำหรับ GCC และอีกครั้งสำหรับคอมไพเลอร์ของผู้จำหน่าย)
เพื่อความปลอดภัย คอมไพเลอร์ที่สร้างโค้ดออบเจ็กต์ที่ไม่เข้ากัน (โค้ดที่ใช้ ABI ต่างกัน เช่น ในส่วนของคลาสและข้อยกเว้น) ควรใช้รูปแบบการแปลงชื่อที่แตกต่างกัน วิธีนี้รับประกันได้ว่าความไม่เข้ากันเหล่านี้จะถูกตรวจพบในขั้นตอนการเชื่อมโยง ไม่ใช่ในระหว่างการทำงานของซอฟต์แวร์ (ซึ่งอาจนำไปสู่ข้อผิดพลาดที่ไม่ชัดเจนและปัญหาเสถียรภาพที่ร้ายแรง)
ด้วยเหตุนี้ การตกแต่งชื่อจึงเป็นส่วนสำคัญของABI ที่เกี่ยวข้องกับ C ++ ทุกประเภท
ในบางกรณี โดยเฉพาะอย่างยิ่งในโค้ดเบสขนาดใหญ่และซับซ้อน การแปลงชื่อที่ถูกบิดเบือนซึ่งปรากฏในข้อความแสดงข้อผิดพลาดของลิงเกอร์ กลับไปยังโทเค็น/ชื่อตัวแปรที่เกี่ยวข้องในซอร์สโค้ด อาจทำได้ยากหรือไม่สามารถทำได้จริง ปัญหานี้อาจทำให้วิศวกรฝ่ายสร้างหรือทดสอบระบุไฟล์ซอร์สโค้ดที่เกี่ยวข้องได้ยากมาก แม้ว่าจะใช้คอมไพเลอร์และลิงเกอร์เพียงตัวเดียวก็ตาม เครื่องมือถอดรหัสชื่อที่ถูกบิดเบือน (รวมถึงเครื่องมือในกลไกการรายงานข้อผิดพลาดของลิงเกอร์) อาจช่วยได้บ้าง แต่กลไกการบิดเบือนชื่อนั้นเองอาจละทิ้งข้อมูลสำคัญที่ช่วยในการแยกแยะความหมายได้
Demangle via c++filt
$ c++filt -n _ZNK3MapI10StringName3RefI8GDScriptE10ComparatorIS0_E16DefaultAllocatorE3hasERKS0_ Map<StringName, Ref<GDScript>, Comparator<StringName>, DefaultAllocator>::has(StringName const&) constถอดรหัสผ่าน ABI ของ GCC ในตัว
import < cxxabi . h > ;import std ;int main ( int argc , char * argv []) { const char * mangledName = "_ZNK3MapI10StringName3RefI8GDScriptE10ComparatorIS0_E16DefaultAllocatorE3hasERKS0_" ; int status = -1 ; char * demangledName = abi :: __cxa_demangle ( mangledName , nullptr , nullptr , & status ); std :: println ( "Demangled: {}" , demangledName ); std :: free ( demangledName ); return 0 ; }ผลลัพธ์:
Demangled: Map<StringName, Ref<GDScript>, Comparator<StringName>, DefaultAllocator>::has(StringName&) constซี#
ภาษา โปรแกรม C#หลีกเลี่ยงการใช้การแปลงชื่อ (name mangling) โดยสิ้นเชิง โดยอาศัยเมตาเดต้าใน รูปแบบ Common Intermediate Languageแทน สัญลักษณ์ C/C++ ที่ไม่ถูกแปลงชื่อ (ทำเครื่องหมายด้วย) สามารถใช้งานได้กับPlatform Invocation Services (P/Invoke ) extern"C"[DllImport]
ดี
ภาษา โปรแกรม Dใช้การแปลงชื่อ ซึ่งแปลงแตกต่างจาก C++ และโดยทั่วไปแล้วจะสร้างชื่อที่อ่านง่ายกว่าสำหรับมนุษย์[ 6 ] [ 7 ]ชื่อจะถูกแปลงเว้นแต่จะเชื่อมโยงกับ C (โดยใช้) มีเครื่องมือ "ddemangle" สำหรับการแปลงชื่อสัญลักษณ์[ 8 ]ไลบรารีมีโมดูลสำหรับการแปลงสัญลักษณ์[ 9 ]และสำหรับการแปลงสัญลักษณ์ D [ 10 ]extern(C)dmd.mangleextern(D)core.demangle
ฟอร์ทราน
การแปลงชื่อตัวแปรก็จำเป็นใน คอมไพเลอร์ ของ Fortranเช่นกัน เดิมทีเป็นเพราะภาษานี้ไม่คำนึงถึงตัวพิมพ์ใหญ่เล็กต่อมามีการเพิ่มข้อกำหนดการแปลงชื่อตัวแปรเพิ่มเติมในมาตรฐาน Fortran 90 เนื่องจากการเพิ่มโมดูลและคุณสมบัติอื่นๆ โดยเฉพาะอย่างยิ่ง การแปลงตัวพิมพ์ใหญ่เล็กเป็นปัญหาทั่วไปที่ต้องจัดการเมื่อเรียกใช้ไลบรารีของ Fortran เช่นLAPACK จากภาษาอื่นๆ เช่นC
เนื่องจากไม่คำนึงถึงตัวพิมพ์ใหญ่เล็ก ชื่อของซับรูทีนหรือฟังก์ชันFOOจะต้องถูกแปลงเป็นตัวพิมพ์ใหญ่และรูปแบบมาตรฐานโดยคอมไพเลอร์ เพื่อให้สามารถเชื่อมโยงได้ในลักษณะเดียวกันโดยไม่คำนึงถึงตัวพิมพ์ใหญ่เล็ก คอมไพเลอร์แต่ละตัวได้นำวิธีการนี้ไปใช้ในรูปแบบต่างๆ และยังไม่มีการกำหนดมาตรฐานใดๆ คอมไพเลอร์ Fortran ของAIXและHP-UXแปลงตัวระบุทั้งหมดเป็นตัวพิมพ์เล็กfooในขณะที่ คอม ไพ เลอร์ Fortran ของ CrayและUnicosแปลงตัวระบุทั้งหมดเป็นตัวพิมพ์ใหญ่ คอมไพเลอร์ FOOGNU g77แปลงตัวระบุเป็นตัวพิมพ์เล็กบวกเครื่องหมายขีดล่างยกเว้นตัวระบุที่มีเครื่องหมายขีดล่างอยู่แล้วจะมีเครื่องหมายขีดล่างสองตัวต่อท้ายตามธรรมเนียมที่กำหนดโดยf2cคอมไพเลอร์อื่นๆ อีกมากมาย รวมถึง คอมไพเลอร์ IRIXของSilicon Graphics (SGI) , GNU Fortranและ คอมไพเลอร์ Fortran ของ Intel (ยกเว้นบน Microsoft Windows) แปลงตัวระบุทั้งหมดเป็นตัวพิมพ์เล็กบวกเครื่องหมายขีดล่าง ( และตามลำดับ) บน Microsoft Windows คอมไพเลอร์ Fortran ของ Intel จะใช้ตัวพิมพ์ใหญ่โดยไม่มีเครื่องหมายขีดล่างเป็นค่าเริ่มต้น[ 11 ]foo_FOO_BARfoo_bar__foo_foo_bar_
ตัวระบุในโมดูล Fortran 90 จะต้องถูกเปลี่ยนแปลงเพิ่มเติม เนื่องจากชื่อขั้นตอนเดียวกันอาจปรากฏในโมดูลที่แตกต่างกัน เนื่องจากมาตรฐาน Fortran 2003 กำหนดให้ชื่อขั้นตอนของโมดูลต้องไม่ขัดแย้งกับสัญลักษณ์ภายนอกอื่นๆ[ 12 ]คอมไพเลอร์จึงมักใช้ชื่อโมดูลและชื่อขั้นตอน โดยมีเครื่องหมายที่แตกต่างกันคั่นกลาง ตัวอย่างเช่น:
โมดูลm ประกอบด้วยฟังก์ชันจำนวนเต็มfive () five = 5 จบฟังก์ชันfive จบโมดูลmในโมดูลนี้ ชื่อของฟังก์ชันจะถูกแปลงเป็นรูปแบบ__m_MOD_five(เช่น GNU Fortran), m_mp_five_(เช่น Intel's ifort), m.five_(เช่น Oracle's sun95) เป็นต้น เนื่องจาก Fortran ไม่อนุญาตให้ใช้ชื่อซ้ำซ้อนกับโปรซีเจอร์ แต่ใช้บล็อกอินเทอร์เฟซทั่วไปและโปรซีเจอร์ที่ผูกกับประเภททั่วไปแทน ดังนั้นชื่อที่ถูกแปลงจึงไม่จำเป็นต้องมีเบาะแสเกี่ยวกับอาร์กิวเมนต์
ตัวเลือก BIND ใน Fortran 2003 ทำให้คอมไพเลอร์ใช้กฎการแปลงรหัสที่ใช้สำหรับภาษาโปรแกรมอื่น เช่น C ดังแสดงในภาพด้านบน
ชวา
ในภาษา Javaลายเซ็นของเมธอดหรือคลาสจะประกอบด้วยชื่อของเมธอดหรือคลาสนั้น รวมถึงชนิดของอาร์กิวเมนต์และค่าส่งคืน (ถ้ามี) รูปแบบของลายเซ็นได้รับการบันทึกไว้ เนื่องจากภาษา คอมไพเลอร์ และรูปแบบไฟล์ .class ถูกออกแบบมาพร้อมกัน (และคำนึงถึงการเขียนโปรแกรมเชิงวัตถุและความสามารถในการทำงานร่วมกันได้ตั้งแต่เริ่มต้น)
สร้างชื่อที่ไม่ซ้ำกันสำหรับคลาสภายในและคลาสที่ไม่ระบุชื่อ
ขอบเขตของคลาสที่ไม่ระบุชื่อนั้นจำกัดอยู่ภายในคลาสแม่ ดังนั้นคอมไพเลอร์จึงต้องสร้างชื่อสาธารณะที่มี "คุณสมบัติ" สำหรับคลาสภายในเพื่อหลีกเลี่ยงความขัดแย้งในกรณีที่มีคลาสอื่นที่มีชื่อเดียวกัน (ไม่ว่าจะเป็นคลาสภายในหรือไม่) อยู่ในเนมสเปซเดียวกัน ในทำนองเดียวกัน คลาสที่ไม่ระบุชื่อจะต้องมีชื่อสาธารณะ "ปลอม" ที่สร้างขึ้นสำหรับพวกมัน (เนื่องจากแนวคิดของคลาสที่ไม่ระบุชื่อมีอยู่เฉพาะในคอมไพเลอร์ ไม่ใช่ในรันไทม์) ดังนั้น การคอมไพล์โปรแกรม Java ต่อไปนี้:
คลาสสาธารณะFoo { คลาสBar { ตัวแปรx สาธารณะ}public void baz () { Object f = new Object () { public String toString () { return "hello" ; } }; } }จะสร้าง ไฟล์ .class จำนวน 3 ไฟล์:
- foo.classซึ่งประกอบด้วยคลาสหลัก (ภายนอก) foo
- foo$bar.classซึ่งประกอบด้วยคลาสภายในที่มีชื่อว่าfoo.bar
- foo$1.classซึ่งประกอบด้วยคลาสภายในที่ไม่ระบุชื่อ (เฉพาะในเมธอดfoo.baz )
ชื่อคลาสทั้งหมดนี้ถูกต้อง (เนื่องจาก สัญลักษณ์ $ได้รับอนุญาตในข้อกำหนดของ JVM) และชื่อเหล่านี้ "ปลอดภัย" สำหรับคอมไพเลอร์ในการสร้าง เนื่องจากคำจำกัดความของภาษา Java แนะนำไม่ให้ใช้ สัญลักษณ์ $ในคำจำกัดความคลาส Java ทั่วไป
การแก้ไขชื่อใน Java นั้นซับซ้อนยิ่งขึ้นในระหว่างการทำงาน เนื่องจากชื่อเต็มของคลาสจะมีเอกลักษณ์เฉพาะภายในอิน สแตนซ์ ของคลาสโหลดเดอร์ เฉพาะเท่านั้น คลาสโหลดเดอร์มีการจัดลำดับตามลำดับชั้น และแต่ละเธรดใน JVM จะมีคลาสโหลดเดอร์บริบทที่เรียกว่า context class loader ดังนั้นในกรณีที่อินสแตนซ์ของคลาสโหลดเดอร์สองตัวที่แตกต่างกันมีคลาสที่มีชื่อเดียวกัน ระบบจะพยายามโหลดคลาสโดยใช้คลาสโหลดเดอร์ราก (หรือคลาสโหลดเดอร์ระบบ) ก่อน จากนั้นจึงลงไปตามลำดับชั้นจนถึงคลาสโหลดเดอร์บริบท
อินเทอร์เฟซเนทีฟ Java
Java Native Interface ( JUI) ซึ่งเป็นส่วนสนับสนุนเมธอดดั้งเดิมของ Java ช่วยให้โปรแกรมที่เขียนด้วยภาษา Java สามารถเรียกใช้โปรแกรมที่เขียนด้วยภาษาอื่น (โดยปกติคือ C หรือ C++) ได้ อย่างไรก็ตาม มีข้อกังวลเกี่ยวกับการแก้ไขชื่ออยู่สองประการ ซึ่งทั้งสองประการนี้ยังไม่ได้ถูกนำไปใช้ใน ลักษณะ ที่เป็นมาตรฐาน :
- การแปลงชื่อ JVM เป็นชื่อเนทีฟ - ดูเหมือนว่าจะมีความเสถียรมากกว่า เนื่องจาก Oracle เปิดเผยแผนการของตนสู่สาธารณะ[ 13 ]
- การแปลงชื่อแบบปกติใน C++ - ดูรายละเอียดด้านบน
ไพธอน
ในPythonการแปลงชื่อ (mangling) ใช้สำหรับแอตทริบิวต์ของคลาสที่ไม่ต้องการให้คลาสย่อยใช้[ 14 ]ซึ่งกำหนดไว้เช่นนั้นโดยการตั้งชื่อที่มีเครื่องหมายขีดล่างนำหน้าสองตัวขึ้นไปและเครื่องหมายขีดล่างตามหลังไม่เกินหนึ่งตัว ตัวอย่างเช่น__thingจะถูกแปลงชื่อ เช่นเดียวกับ___thingและ__thing_แต่__thing__และ__thing___จะไม่ถูกแปลงชื่อ รันไทม์ของ Python ไม่จำกัดการเข้าถึงแอตทริบิวต์ดังกล่าว การแปลงชื่อจะป้องกันการชนกันของชื่อเฉพาะในกรณีที่คลาสที่สืบทอดมากำหนดแอตทริบิวต์ที่มีชื่อเดียวกันเท่านั้น
เมื่อพบแอตทริบิวต์ที่มีชื่อผิดเพี้ยน Python จะแปลงชื่อเหล่านั้นโดยการเพิ่มเครื่องหมายขีดล่างเดี่ยวและชื่อของคลาสที่ครอบคลุมอยู่ด้านหน้า ตัวอย่างเช่น:
คลาสTest : def __mangled_name ( self ) -> None : passdef normal_name ( self ) -> None : passถ้า__name == "__main__" : t : Test = Test () print ([ attr for attr in dir ( t ) if "name" in attr ]) # พิมพ์: ['_Test__mangled_name', 'normal_name']ปาสคาล
เทอร์โบ ปาสคาล, เดลฟี
เพื่อหลีกเลี่ยงการแปลงชื่อไฟล์ในภาษา Pascal ให้ใช้:
ส่งออกฟังก์ชันmyFunc ชื่อ'myFunc' และฟังก์ชัน myProc ชื่อ'myProc' ;ปาสคาลเสรี
ภาษา Free Pascalรองรับ การโอเวอร์โหลด ฟังก์ชันและตัวดำเนินการ ดังนั้นจึงใช้การแปลงชื่อ (name mangling) เพื่อรองรับคุณสมบัติเหล่านี้ ในทางกลับกัน Free Pascal สามารถเรียกใช้สัญลักษณ์ที่กำหนดไว้ในโมดูลภายนอกที่สร้างด้วยภาษาอื่น และส่งออกสัญลักษณ์ของตนเองเพื่อให้ภาษาอื่นเรียกใช้ได้ สำหรับข้อมูลเพิ่มเติม โปรดดูบทที่ 6.2และ7.1ของคู่มือโปรแกรมเมอร์ Free Pascal
ออบเจกทีฟซี
โดยพื้นฐานแล้ว เมธอดในObjective-C มีอยู่สองรูปแบบ คือ เมธอด ของคลาส("static")และเมธอดของอินสแตนซ์การประกาศเมธอดใน Objective-C มีรูปแบบดังนี้:
+ ( return-type ) name 0 : parameter 0 name 1 : parameter 1 ... – ( return-type ) name 0 : parameter 0 name 1 : parameter 1 ...
เมธอดของคลาสจะใช้เครื่องหมาย + ส่วนเมธอดของอินสแตนซ์จะใช้เครื่องหมาย - ตัวอย่างการประกาศเมธอดของคลาสโดยทั่วไปอาจมีลักษณะดังนี้:
+ ( id ) initWithX: ( int ) number andY: ( int ) number ; + ( id ) new ;โดยที่เมธอดอินสแตนซ์มีลักษณะดังนี้:
- ( id ) value ; - ( id ) setValue: ( id ) new_value ;การประกาศเมธอดแต่ละแบบจะมีรูปแบบการแสดงผลภายในที่เฉพาะเจาะจง เมื่อคอมไพล์แล้ว แต่ละเมธอดจะได้รับการตั้งชื่อตามรูปแบบต่อไปนี้สำหรับเมธอดของคลาส:
_c_ คลาส _ ชื่อ0 _ ชื่อ1 _ ...
และนี่คือตัวอย่างวิธีการ:
_i_ คลาส _ ชื่อ0 _ ชื่อ1 _ ...
เครื่องหมายโคลอนในไวยากรณ์ Objective-C จะถูกแปลงเป็นเครื่องหมายขีดล่าง ดังนั้น เมธอดของคลาสใน Objective-C หากเป็นของคลาส จะถูกแปลงเป็น`<br>` และเมธอดของอินสแตนซ์ (ซึ่งเป็นของคลาสเดียวกัน) จะถูกแปลงเป็น `<br> ` +(id)initWithX:(int)numberandY:(int)number;Point_c_Point_initWithX_andY_-(id)value;_i_Point_value
แต่ละเมธอดของคลาสจะถูกกำหนดป้ายกำกับในลักษณะนี้ อย่างไรก็ตาม การค้นหาเมธอดที่คลาสอาจตอบสนองนั้นจะยุ่งยากหากเมธอดทั้งหมดถูกแสดงในลักษณะนี้ แต่ละเมธอดจะถูกกำหนดสัญลักษณ์เฉพาะ (เช่น จำนวนเต็ม) สัญลักษณ์ดังกล่าวเรียกว่า ตัวเลือก (selector ) ใน Objective-C เราสามารถจัดการตัวเลือกได้โดยตรง – ตัวเลือกมีประเภทเฉพาะใน Objective-C – SEL.
ในระหว่างการคอมไพล์ จะมีการสร้างตารางที่แมปการแสดงผลในรูปแบบข้อความ เช่น_i_Point_valueไปยังตัวเลือก (ซึ่งจะได้รับประเภท ) การจัดการตัวเลือกนั้นมีประสิทธิภาพมากกว่าการจัดการการแสดงผลในรูปแบบข้อความของเมธอด โปรดทราบว่าตัวเลือกจะตรงกับชื่อของเมธอดเท่านั้น ไม่ใช่คลาสที่เมธอดนั้นสังกัดอยู่ คลาสที่แตกต่างกันอาจมีการใช้งานที่แตกต่างกันของเมธอดที่มีชื่อเดียวกัน ด้วยเหตุนี้ การใช้งานของเมธอดจึงได้รับตัวระบุเฉพาะเช่น กัน SELซึ่งเรียกว่าตัวชี้การใช้งาน และจะได้รับประเภทด้วยเช่นกันIMP
ข้อความที่ส่งจะถูกเข้ารหัสโดยคอมไพเลอร์เป็นการเรียกใช้ฟังก์ชัน หรือฟังก์ชันที่คล้ายคลึงกัน โดยที่คือผู้รับข้อความ และกำหนดเมธอดที่จะเรียกใช้ แต่ละคลาสจะมีตารางของตัวเองที่แมปตัวเลือกกับส่วนการใช้งาน – ตัวชี้การใช้งานจะระบุตำแหน่งในหน่วยความจำที่ส่วนการใช้งานของเมธอดอยู่ มีตารางแยกต่างหากสำหรับเมธอดของคลาสและเมธอดของอินสแตนซ์ นอกเหนือจากการจัดเก็บไว้ใน ตารางค้นหา แล้วฟังก์ชันต่างๆ โดยพื้นฐานแล้วเป็นฟังก์ชันนิรนาม idobjc_msgSend(idreceiver,SELselector,...)receiverSELSELIMP
ค่าSELของตัวเลือก (selector) จะไม่แตกต่างกันระหว่างคลาสต่างๆ ซึ่งทำให้สามารถใช้งานโพลีมอร์ฟิซึมได้
ระบบรันไทม์ของ Objective-C จะเก็บรักษาข้อมูลเกี่ยวกับประเภทของอาร์กิวเมนต์และค่าส่งคืนของเมธอด อย่างไรก็ตาม ข้อมูลนี้ไม่ได้เป็นส่วนหนึ่งของชื่อเมธอด และอาจแตกต่างกันไปในแต่ละคลาส
เนื่องจาก Objective-C ไม่รองรับเนมสเปซจึงไม่จำเป็นต้องทำการแปลงชื่อคลาส (ซึ่งจะปรากฏเป็นสัญลักษณ์ในไฟล์ไบนารีที่สร้างขึ้น)
สนิม
ชื่อฟังก์ชันจะถูกแปลงโดยค่าเริ่มต้นในRustอย่างไรก็ตาม สามารถปิดใช้งานได้โดยใช้#[no_mangle]แอตทริบิวต์ฟังก์ชัน แอตทริบิวต์นี้สามารถใช้เพื่อส่งออกฟังก์ชันไปยัง C, C++ หรือObjective-Cได้[ 15 ]นอกจากนี้ เมื่อใช้ร่วมกับ#[start]แอตทริบิวต์ฟังก์ชันหรือ#![no_main]แอตทริบิวต์ crate จะช่วยให้ผู้ใช้สามารถกำหนดจุดเริ่มต้นแบบ C สำหรับโปรแกรมได้[ 16 ]
Rust ใช้รูปแบบการแปลงสัญลักษณ์หลายเวอร์ชัน ซึ่งสามารถเลือกได้ในระหว่างการคอมไพล์โดยใช้-Z symbol-mangling-versionตัวเลือก รูปแบบการแปลงสัญลักษณ์ต่อไปนี้ได้รับการกำหนดไว้:
legacyการแปลงรูปแบบ C++ ตาม Itanium IA-64 C++ ABI สัญลักษณ์เริ่มต้นด้วย_ZNและใช้แฮชชื่อไฟล์เพื่อแยกความหมาย ใช้ตั้งแต่ Rust 1.9 [ 17 ]v0เวอร์ชันปรับปรุงของรูปแบบเดิม พร้อมการเปลี่ยนแปลงสำหรับ Rust สัญลักษณ์เริ่มต้นด้วย_R. สามารถเข้ารหัสโพลีมอร์ฟิซึมได้ ฟังก์ชันไม่มีการเข้ารหัสประเภทการส่งคืน (Rust ไม่มีการโอเวอร์โหลด) ชื่อ Unicode ใช้punycode ที่แก้ไขแล้ว การบีบอัด (การอ้างอิงย้อนกลับ) ใช้การกำหนดแอดเดรสแบบไบต์ ใช้ตั้งแต่ Rust 1.37 [ 18 ]
ตัวอย่างมีให้ในsymbol-namesการทดสอบ Rust [ 19 ]
สวิฟต์
Swiftเก็บข้อมูลเมตาเกี่ยวกับฟังก์ชัน (และอื่นๆ) ไว้ในสัญลักษณ์ที่ถูกแปลง (mangled symbols) ที่อ้างถึงฟังก์ชันเหล่านั้น ข้อมูลเมตานี้ประกอบด้วยชื่อฟังก์ชัน คุณลักษณะ ชื่อโมดูล ประเภทพารามิเตอร์ ประเภทค่าส่งคืน และอื่นๆ ตัวอย่างเช่น:
ชื่อที่ถูกบิดเบือนสำหรับเมธอดfunc calculate(x: int) -> intของMyClassคลาสในโมดูลtestคือ_TFC4test7MyClass9calculatefS0_FT1xSi_Siสำหรับ Swift เวอร์ชัน 2014 ส่วนประกอบและความหมายมีดังนี้: [ 20 ]
_T: คำนำหน้าสำหรับสัญลักษณ์ Swift ทั้งหมด ทุกอย่างจะเริ่มต้นด้วยคำนี้F: ฟังก์ชันที่ไม่ใช้เคอร์เนลC: ฟังก์ชันของคลาส เช่น เมธอด4testชื่อโมดูล ตามด้วยความยาวของโมดูล7MyClass: ชื่อคลาสที่ฟังก์ชันนั้นสังกัดอยู่ โดยมีคำนำหน้าเป็นความยาวของฟังก์ชันนั้น9calculate: ชื่อฟังก์ชัน ตามด้วยความยาวของฟังก์ชันf: คุณสมบัติฟังก์ชัน ในกรณีนี้คือ 'f' ซึ่งหมายถึงฟังก์ชันปกติS0: กำหนดให้ประเภทของพารามิเตอร์ตัวแรก (ซึ่งก็คืออินสแตนซ์ของคลาส) เป็นประเภทแรกในสแต็กประเภท (ในที่นี้MyClassไม่ได้ซ้อนกัน ดังนั้นจึงมีดัชนีเป็น 0)_FT: นี่คือจุดเริ่มต้นของรายการประเภทสำหรับทูเปิลพารามิเตอร์ของฟังก์ชัน1x: ชื่อภายนอกของพารามิเตอร์ตัวแรกของฟังก์ชันSi: ระบุชนิดข้อมูล Swift.Int ในตัวสำหรับพารามิเตอร์แรก_Si: ประเภทค่าส่งคืน: Swift.Int อีกครั้ง
การแปลงเวอร์ชันตั้งแต่ Swift 4.0 ได้รับการบันทึกอย่างเป็นทางการแล้ว โดยยังคงมีความคล้ายคลึงกับ Itanium อยู่บ้าง[ 21 ]
ดูเพิ่มเติม
- อินเทอร์เฟซการเขียนโปรแกรมแอปพลิเคชัน (API)
- อินเทอร์เฟซไบนารีแอปพลิเคชัน (ABI)
- การประชุมทางโทรศัพท์
- การเปรียบเทียบซอฟต์แวร์การจำลองเสมือนแอปพลิเคชัน (เช่น เครื่องเสมือน)
- อินเทอร์เฟซฟังก์ชันภายนอก (FFI)
- Java Native Interface (JNI)
- การผูกภาษา
- การหยุด
- สวีจี
ลิงก์ภายนอก
- Linux Itanium ABI สำหรับ C++รวมถึงรูปแบบการแปลงชื่อ (name mangling scheme)
- ข้อกำหนดมาตรฐาน ABI ของ Macintosh C/C++
- c++filt – ตัวกรองสำหรับถอดรหัสสัญลักษณ์ C++ ที่เข้ารหัสไว้สำหรับคอมไพเลอร์ GNU/Intel
- undname – เครื่องมือ msvc สำหรับถอดรหัสชื่อไฟล์
- demangler.com – เครื่องมือออนไลน์สำหรับถอดรหัสสัญลักษณ์ C++ ของ GCC และ MSVC
- ระบบรันไทม์ของ Objective-C – จากหนังสือ The Objective-C Programming Language 1.0ของ Apple
- เอกสาร "Calling conventions for different C++ compilers"โดยAgner Fogมีคำอธิบายโดยละเอียดเกี่ยวกับรูปแบบการแปลงชื่อ (name mangling schemes) สำหรับคอมไพเลอร์ C++ x86 และ x64 ต่างๆ (หน้า 24–42 ในฉบับวันที่ 8 มิถุนายน 2011)
- การแปลงชื่อ/การถอดรหัสชื่อใน C++คำอธิบายโดยละเอียดเกี่ยวกับการแปลงชื่อในคอมไพเลอร์ Visual C++
- PHP UnDecorateSymbolNameเป็นสคริปต์ PHP ที่ใช้ถอดรหัสชื่อฟังก์ชันของ Microsoft Visual C
- การผสมผสานโค้ด C และ C++
- เลวีน, จอห์น อาร์. (2000) [ตุลาคม 1999]. "บทที่ 5: การจัดการสัญลักษณ์". ตัวเชื่อมโยงและตัวโหลด . ชุดหนังสือวิศวกรรมซอฟต์แวร์และการเขียนโปรแกรมของมอร์แกน คอฟแมน (ฉบับที่ 1). ซานฟรานซิสโก สหรัฐอเมริกา: มอร์แกน คอฟแมน . ISBN 1-55860-496-0. OCLC 42413382 . สืบค้นเมื่อ2020-01-12 .
{{cite book}}: CS1 maint: deprecated archival service (link)รหัส: [1] [2]ข้อผิดพลาด: [3] - การบิดเบือนชื่อถูกไขความลับโดย Fivos Kefallonitis