เหตุใดฟังก์ชัน C จึงไม่สามารถเปลี่ยนชื่อได้


137

ฉันมีการสัมภาษณ์เมื่อเร็ว ๆ นี้และมีคำถามหนึ่งที่ถามคือการใช้extern "C"รหัส C ++ คืออะไร ฉันตอบว่ามันคือการใช้ฟังก์ชั่น C ในรหัส C ++ เนื่องจาก C ไม่ใช้ name-mangling ฉันถูกถามว่าทำไม C ถึงไม่ใช้ชื่อเล่นและพูดตามตรงว่าฉันไม่สามารถตอบได้

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

คำถามของฉันคือ: เกิดอะไรขึ้นกับการอนุญาตให้คอมไพเลอร์ C ++ ทำลายฟังก์ชัน C ด้วย ฉันจะเดาว่ามันไม่สำคัญว่าคอมไพเลอร์จะตั้งชื่ออะไรให้กับพวกเขา เราเรียกฟังก์ชันในลักษณะเดียวกันในภาษา C และ C ++


75
C ไม่จำเป็นต้องทำให้ชื่อยุ่งเหยิงเพราะไม่มีฟังก์ชันมากเกินไป
EOF

9
คุณจะเชื่อมโยงไลบรารี C กับโค้ด C ++ ได้อย่างไรหากคอมไพเลอร์ C ++ เปลี่ยนชื่อฟังก์ชัน
จ้า

6
"ฉันตอบว่ามันคือการใช้ฟังก์ชัน C ในรหัส C ++ เนื่องจาก C ไม่ได้ใช้การเข้ารหัสชื่อ" - ฉันคิดว่ามันเป็นอีกทางหนึ่ง Extern "C" ทำให้ฟังก์ชัน C ++ สามารถใช้งานได้ในคอมไพเลอร์ C ที่มา
rozina

3
@ Engineer999: และถ้าคุณคอมไพล์เซ็ตย่อยของ C ที่เป็น C ++ ด้วยคอมไพเลอร์ C ++ ชื่อฟังก์ชันก็จะแหลกเหลวแน่นอน แต่ถ้าคุณต้องการที่จะสามารถเชื่อมโยงไบนารีที่สร้างขึ้นด้วยคอมไพเลอร์ที่แตกต่างกันคุณไม่ต้องการให้ชื่อสับสน
EOF

13
C ไม่ชื่อฉีก โดยทั่วไปชื่อที่สับสนคือชื่อของฟังก์ชันที่นำหน้าด้วยเครื่องหมายขีดล่าง บางครั้งก็เป็นชื่อของฟังก์ชันตามด้วยขีดล่าง extern "C"กล่าวว่าให้ใช้ชื่อแบบเดียวกับที่คอมไพเลอร์ "the" ต้องการ
Pete Becker

คำตอบ:


189

มันเป็นคำตอบข้างต้น แต่ฉันจะพยายามทำให้ทุกอย่างเป็นบริบท

อันดับแรกซีมาก่อน ดังนั้นสิ่งที่ C ทำคือ "ค่าเริ่มต้น" มันไม่ได้โกงชื่อเพราะมันไม่ได้ ชื่อฟังก์ชันคือชื่อฟังก์ชัน ทั่วโลกคือโลกและอื่น ๆ

จากนั้น C ++ ก็เข้ามา C ++ ต้องการให้สามารถใช้ตัวเชื่อมโยงเดียวกันกับ C และสามารถเชื่อมโยงกับโค้ดที่เขียนด้วยภาษา C ได้ แต่ C ++ ไม่สามารถปล่อยให้ C "ยุ่งเหยิง" (หรือขาด) ได้ตามที่เป็นอยู่ ลองดูตัวอย่างต่อไปนี้:

int function(int a);
int function();

ใน C ++ เป็นฟังก์ชันที่แตกต่างกันโดยมีเนื้อความที่แตกต่างกัน หากไม่มีสิ่งใดถูกหักงอทั้งสองจะถูกเรียกว่า "function" (หรือ "_function") และผู้เชื่อมโยงจะบ่นเกี่ยวกับการกำหนดสัญลักษณ์ใหม่ วิธีแก้ปัญหา C ++ คือการแยกประเภทอาร์กิวเมนต์ลงในชื่อฟังก์ชัน ดังนั้นคนหนึ่งถูกเรียก_function_intและอีกคนหนึ่งเรียกว่า_function_void(ไม่ใช่รูปแบบการโกงกินที่แท้จริง) และหลีกเลี่ยงการชนกัน

ตอนนี้เราเหลือปัญหา หากint function(int a)ได้รับการกำหนดไว้ในโมดูลเซลเซียสและเรากำลังเพียงการส่วนหัวของมัน (เช่นการประกาศ) ใน C ++ _function_intรหัสและใช้มันคอมไพเลอร์จะสร้างการเรียนการสอนให้กับตัวเชื่อมโยงที่จะนำเข้า เมื่อกำหนดฟังก์ชันในโมดูล C จะไม่เรียกสิ่งนั้น _functionมันถูกเรียกว่า สิ่งนี้จะทำให้เกิดข้อผิดพลาดตัวเชื่อมโยง

เพื่อหลีกเลี่ยงข้อผิดพลาดนั้นในระหว่างการประกาศฟังก์ชันเราจะบอกคอมไพเลอร์ว่าเป็นฟังก์ชันที่ออกแบบมาเพื่อเชื่อมโยงหรือคอมไพเลอร์โดยคอมไพเลอร์ C:

extern "C" int function(int a);

ขณะนี้คอมไพเลอร์ C ++ รู้ที่จะนำเข้า_functionมากกว่า_function_intและทุกอย่างเรียบร้อยดี


1
@ShacharShamesh: ฉันเคยถามสิ่งนี้ที่อื่น แต่แล้วการเชื่อมโยงในไลบรารีที่คอมไพล์ C ++ ล่ะ? เมื่อคอมไพเลอร์กำลังก้าวผ่านและรวบรวมโค้ดของฉันซึ่งเรียกใช้ฟังก์ชันหนึ่งในไลบรารีที่คอมไพล์ C ++ จะรู้ได้อย่างไรว่าชื่อใดที่จะทำลายหรือมอบให้กับฟังก์ชันเมื่อเห็นการประกาศหรือการเรียกใช้ฟังก์ชัน จะรู้ได้อย่างไรว่ามันถูกกำหนดที่ใดชื่อนั้นถูกทำให้สับสนกับสิ่งอื่น? ดังนั้นจึงต้องมีวิธีการตั้งชื่อมาตรฐานใน C ++?
Engineer999

2
ทุกคอมไพเลอร์ทำด้วยวิธีพิเศษของตัวเอง หากคุณกำลังรวบรวมทุกอย่างด้วยคอมไพเลอร์เดียวกันก็ไม่สำคัญ แต่ถ้าคุณพยายามใช้เช่นห้องสมุดที่รวบรวมด้วยคอมไพเลอร์ของ Borland จากโปรแกรมที่คุณสร้างด้วยคอมไพเลอร์ของ Microsoft ก็ ... โชคดี คุณจะต้องใช้ :)
Mark VY

6
@ Engineer999 เคยสงสัยไหมว่าทำไมไม่มีไลบรารี C ++ แบบพกพา แต่พวกเขาระบุเวอร์ชัน (และแฟล็ก) ของคอมไพเลอร์ (และไลบรารีมาตรฐาน) ที่คุณต้องใช้หรือเพียงแค่ส่งออก C API ไปเลย C ++ เป็นภาษาพกพาที่น้อยที่สุดเท่าที่เคยมีมาในขณะที่ C นั้นตรงกันข้าม มีความพยายามในเรื่องนี้ แต่สำหรับตอนนี้หากคุณต้องการสิ่งที่พกพาได้จริงคุณจะต้องติดกับ C.
Voo

1
@Voo ในทางทฤษฎีคุณควรจะสามารถเขียนโค้ดแบบพกพาได้เพียงแค่ปฏิบัติตามมาตรฐานเช่น-std=c++11และหลีกเลี่ยงการใช้สิ่งที่อยู่นอกมาตรฐาน เช่นเดียวกับการประกาศเวอร์ชัน Java (แม้ว่าเวอร์ชัน Java ที่ใหม่กว่าจะเข้ากันได้แบบย้อนหลังก็ตาม) ไม่ใช่ความผิดมาตรฐานที่ผู้คนใช้ส่วนขยายเฉพาะของคอมไพเลอร์และรหัสขึ้นอยู่กับแพลตฟอร์ม ในทางกลับกันคุณไม่สามารถตำหนิพวกเขาได้เนื่องจากมีหลายสิ่งหลายอย่าง (โดยเฉพาะ IO เช่นซ็อกเก็ต) ขาดหายไปในมาตรฐาน คณะกรรมการดูจะจับได้ช้า แก้ไขฉันถ้าฉันพลาดอะไรไป
mucaho

14
@mucaho: คุณกำลังพูดถึงการพกพาต้นทาง / ความเข้ากันได้ เช่น API Voo กำลังพูดถึงความเข้ากันได้ของไบนารีโดยไม่ต้องคอมไพล์ใหม่ เรื่องนี้ต้องมีการทำงานร่วมกัน ABI คอมไพเลอร์ C ++ เปลี่ยน ABI ระหว่างเวอร์ชันเป็นประจำ (เช่น g ++ ไม่ได้พยายามที่จะมี ABI ที่เสถียรฉันคิดว่าพวกเขาไม่ได้ทำลาย ABI เพียงเพื่อความสนุกสนาน แต่พวกเขาไม่ได้หลีกเลี่ยงการเปลี่ยนแปลงที่ต้องมีการเปลี่ยนแปลง ABI เมื่อมีบางอย่างที่จะได้รับและไม่มีวิธีอื่นที่ดี ที่จะทำ).
Peter Cordes

45

มันไม่ใช่ว่าพวกเขา "ไม่สามารถ" พวกเขาไม่ได้โดยทั่วไป

หากคุณต้องการเรียกใช้ฟังก์ชันในไลบรารี C ที่เรียกว่าfoo(int x, const char *y)ไม่เป็นการดีที่จะปล่อยให้คอมไพเลอร์ C ++ ของคุณเข้ามายุ่งfoo_I_cCP()(หรืออะไรก็ตามเพียงแค่สร้างโครงร่างที่ยุ่งเหยิงในจุดที่นี่) เพียงเพราะมันทำได้

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

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


นี่คือส่วนที่ฉันขาดหายไป เหตุใดคอมไพลเลอร์ C ++ จึงเปลี่ยนชื่อฟังก์ชันเมื่อเห็นการประกาศเพียงหรือเห็นว่ามีการเรียกใช้ มันไม่ใช่แค่ชื่อฟังก์ชันที่ยุ่งเหยิงเมื่อเห็นการนำไปใช้งานหรือไม่? สิ่งนี้จะสมเหตุสมผลสำหรับฉันมากขึ้น
Engineer999

13
@ Engineer999: คุณมีชื่อหนึ่งสำหรับคำจำกัดความและอีกชื่อหนึ่งสำหรับการประกาศได้อย่างไร? "มีฟังก์ชันที่เรียกว่า Brian ที่คุณสามารถโทรหาได้" "โอเคฉันจะโทรหาไบรอัน" "ขออภัยไม่มีฟังก์ชันที่เรียกว่า Brian" ปรากฎว่ามันเรียกว่า Graham
Lightness Races ใน Orbit

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

1
@ Engineer999 ทั้งคู่ต้องเห็นด้วยกับการโกงกินเดียวกัน ดังนั้นพวกเขาจึงเห็นไฟล์ส่วนหัว (โปรดจำไว้ว่ามีข้อมูลเมตาน้อยมากใน DLL ดั้งเดิม - ส่วนหัวเป็นข้อมูลเมตา) และไปที่ "อาใช่ไบรอันน่าจะเป็นเกรแฮมจริงๆ" หากวิธีนี้ไม่ได้ผล (เช่นมีสองรูปแบบที่เข้ากันไม่ได้) คุณจะไม่ได้รับลิงค์ที่ถูกต้องและแอปพลิเคชันของคุณจะล้มเหลว C ++ มีความเข้ากันไม่ได้มากมายเช่นนี้ ในทางปฏิบัติคุณจะต้องใช้ชื่อที่ยุ่งเหยิงอย่างชัดเจนและปิดการใช้งานการโกงที่ด้านข้างของคุณ (เช่นคุณบอกรหัสของคุณเพื่อเรียกใช้งาน Graham ไม่ใช่ Brian) ในการปฏิบัติจริง ... extern "C":)
Luaan

1
@ Engineer999 ฉันอาจจะคิดผิด แต่คุณอาจมีประสบการณ์กับภาษาเช่น Visual Basic, C # หรือ Java (หรือแม้แต่ Pascal / Delphi ในระดับหนึ่ง)? สิ่งเหล่านี้ทำให้การทำงานร่วมกันดูเหมือนง่ายมาก ใน C และโดยเฉพาะอย่างยิ่ง C ++ มันเป็นอะไรก็ได้ แต่. มีรูปแบบการโทรมากมายที่คุณต้องให้เกียรติคุณต้องรู้ว่าใครรับผิดชอบหน่วยความจำอะไรและคุณต้องมีไฟล์ส่วนหัวที่บอกการประกาศฟังก์ชันเนื่องจาก DLL เองไม่มีข้อมูลเพียงพอโดยเฉพาะอย่างยิ่งในกรณีของ บริสุทธิ์ C. หากคุณไม่มีไฟล์ส่วนหัวโดยทั่วไปคุณต้องถอดรหัส DLL เพื่อใช้งาน
Luaan

32

เกิดอะไรขึ้นกับการอนุญาตให้คอมไพเลอร์ C ++ ทำลายฟังก์ชัน C ด้วย

พวกเขาจะไม่เป็นฟังก์ชัน C อีกต่อไป

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

เช่นเดียวกับ C ABI ในระบบของคุณ C ABI บางตัวมีรูปแบบการเปลี่ยนชื่อ (เช่น Visual Studio) ดังนั้นจึงเป็นเรื่องเกี่ยวกับ "การปิดการโกงชื่อ" น้อยกว่าและอื่น ๆ เกี่ยวกับการเปลี่ยนจาก C ++ ABI เป็น C ABI สำหรับฟังก์ชันบางอย่าง เราทำเครื่องหมายฟังก์ชัน C ว่าเป็นฟังก์ชัน C ซึ่งเกี่ยวข้องกับ C ABI (แทนที่จะเป็น C ++ ABI) คำประกาศต้องตรงกับคำจำกัดความ (ไม่ว่าจะเป็นในโปรเจ็กต์เดียวกันหรือในไลบรารีของบุคคลที่สามบางส่วน) มิฉะนั้นการประกาศจะไม่มีจุดหมาย หากไม่มีสิ่งนั้นระบบของคุณก็จะไม่รู้วิธีค้นหา / เรียกใช้ฟังก์ชันเหล่านั้น

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


2
+int(PI/3)แต่มีหนึ่งเม็ดเกลือ: ฉันจะระมัดระวังมากที่จะพูดถึง "C ++ ABI" ... AFAIK มีความพยายามในการที่กำหนด c ++ ABIs แต่ไม่มีจริง พฤตินัย / นิตินัยมาตรฐาน - เป็นisocpp.org/files /papers/n4028.pdfรัฐ (และฉันเห็นด้วยอย่างสุดใจ) อ้างเป็นเรื่องน่าขันอย่างยิ่งที่ C ++ สนับสนุนวิธีการเผยแพร่ API ที่มี ABI ไบนารีที่เสถียรเสมอมาโดยใช้ชุดย่อย C ของ C ++ ผ่านภายนอก“ C ”. . C++ Itanium ABIก็แค่นั้น - C ++ ABI บางตัวสำหรับ Itanium ... ตามที่กล่าวไว้ในstackoverflow.com/questions/7492180/c-abi-issues-list

3
@vaxquis: ใช่ไม่ใช่ ABI ของ "C ++" แต่เป็น "C ++ ABI" แบบเดียวกับที่ฉันมี "กุญแจบ้าน" ที่ใช้ไม่ได้กับบ้านทุกหลัง คิดว่ามันอาจจะเป็นที่ชัดเจน แต่ผมพยายามที่จะทำให้มันเป็นที่ชัดเจนเป็นไปได้โดยเริ่มต้นออกด้วยวลี"c ++ ABI ในการใช้งานโดยระบบของคุณ " ฉันทิ้งตัวชี้แจงในคำพูดในภายหลังเพื่อความกระชับ แต่ฉันจะยอมรับการแก้ไขที่ช่วยลดความสับสนที่นี่!
Lightness Races ใน Orbit

1
AIUI C abi มีแนวโน้มที่จะเป็นคุณสมบัติของแพลตฟอร์มในขณะที่ C ++ ABI มักจะเป็นคุณสมบัติของคอมไพเลอร์แต่ละตัวและมักจะเป็นคุณสมบัติของคอมไพเลอร์แต่ละเวอร์ชัน ดังนั้นหากคุณต้องการเชื่อมโยงระหว่างโมดูลที่สร้างด้วยเครื่องมือของผู้จำหน่ายต่างๆคุณต้องใช้ C abi สำหรับอินเทอร์เฟซ
Plugwash

คำสั่ง "ฟังก์ชั่นที่แตกชื่อจะไม่เป็นฟังก์ชัน C อีกต่อไป" นั้นเกินความจริง - เป็นไปได้อย่างสมบูรณ์แบบที่จะเรียกฟังก์ชันที่มีชื่อแตกต่างจากวานิลลา C ธรรมดาหากทราบชื่อที่ไม่สมบูรณ์ การเปลี่ยนชื่อไม่ได้ทำให้การยึดติดกับ C ABI น้อยลงกล่าวคือไม่ทำให้ฟังก์ชัน C น้อยลง อีกวิธีหนึ่งก็สมเหตุสมผลกว่า - รหัส C ++ ไม่สามารถเรียกฟังก์ชัน C ได้โดยไม่ต้องประกาศว่า "C" เพราะจะทำให้ชื่อยุ่งเหยิงเมื่อพยายามเชื่อมโยงกับ callee
Peter - Reinstate Monica

@ PeterA.Schneider: ใช่วลีพาดหัวข่าวเกินจริง ส่วนที่เหลือทั้งหมดของคำตอบที่มีรายละเอียดข้อเท็จจริงที่เกี่ยวข้อง
Lightness Races ใน Orbit

21

MSVC ในความเป็นจริงไม่ชื่อ C ฉีกแม้ว่าในแฟชั่นที่เรียบง่าย บางครั้งก็ต่อท้าย@4หรือตัวเลขอื่น ๆ สิ่งนี้เกี่ยวข้องกับการเรียกประชุมและความจำเป็นในการล้างข้อมูลสแต็ก

ดังนั้นหลักฐานจึงมีข้อบกพร่อง


2
นั่นไม่ใช่การโกงกินชื่อจริงๆ เป็นเพียงรูปแบบการตั้งชื่อเฉพาะของผู้ขาย (หรือการประดับชื่อ) เพื่อป้องกันปัญหาเกี่ยวกับไฟล์ปฏิบัติการที่เชื่อมโยงกับ DLL ที่สร้างขึ้นด้วยฟังก์ชันที่มีรูปแบบการเรียกที่แตกต่างกัน
ปีเตอร์

2
แล้วการเติมเงินด้วย a _?
OrangeDog

12
@ ปีเตอร์: แท้จริงแล้วสิ่งเดียวกัน
Lightness Races ใน Orbit

5
@Frankie_C: "ผู้โทรทำความสะอาดสแต็ก" ไม่ได้ระบุโดยมาตรฐาน C ใด ๆ : รูปแบบการเรียกไม่มีมาตรฐานมากกว่าแบบอื่นจากมุมมองของภาษา
Ben Voigt

2
และจากมุมมอง MSVC ที่ "โทรประชุมมาตรฐาน" /Gd, /Gr, /Gv, /Gzเป็นเพียงสิ่งที่คุณเลือกจาก (กล่าวคือรูปแบบการเรียกมาตรฐานคือสิ่งที่ใช้เว้นแต่การประกาศฟังก์ชันจะระบุรูปแบบการโทรอย่างชัดเจน) คุณกำลังคิดว่าแบบ__cdeclใดเป็นมาตรฐานการโทรเริ่มต้น
MSalters

13

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

บนแพลตฟอร์มส่วนใหญ่มีข้อกำหนด - มักเรียกว่า ABI [Application Binary Interface] ซึ่งอธิบายถึงสิ่งที่คอมไพเลอร์ต้องทำเพื่อสร้างฟังก์ชันที่มีชื่อเฉพาะซึ่งยอมรับอาร์กิวเมนต์ของบางประเภทและส่งคืนค่าของบางประเภทโดยเฉพาะ ในบางกรณี ABI อาจกำหนด "รูปแบบการเรียก" มากกว่าหนึ่ง; คอมไพเลอร์สำหรับระบบดังกล่าวมักจะให้วิธีการระบุว่าควรใช้รูปแบบการเรียกใดสำหรับฟังก์ชันเฉพาะ ตัวอย่างเช่นใน Macintosh กิจวัตรของ Toolbox ส่วนใหญ่จะใช้รูปแบบการเรียก Pascal ดังนั้นต้นแบบของสิ่งต่างๆเช่น "LineTo" จะเป็นดังนี้:

/* Note that there are no underscores before the "pascal" keyword because
   the Toolbox was written in the early 1980s, before the Standard and its
   underscore convention were published */
pascal void LineTo(short x, short y);

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


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

2
@ user34660: ไม่ใช่ qutie เป็นเหตุผลที่ C ไม่สามารถกำหนดการมีอยู่ของคุณลักษณะที่การใช้งานจะต้องใช้ชื่อที่ส่งออกได้หรืออนุญาตให้มีสัญลักษณ์ที่เหมือนกันหลายชื่อที่แตกต่างกันไปตามลักษณะรอง
supercat

เรารู้หรือไม่ว่ามีความพยายามที่จะ "มอบอำนาจ" ให้กับสิ่งนั้น ๆ หรือสิ่งเหล่านั้นเป็นส่วนขยายสำหรับ C ก่อน C ++
user34660

@ user34660: Re "การเชื่อมโยงแบบคงที่ดูเหมือนดั้งเดิมสำหรับโปรแกรมเมอร์ Windows ... " แต่การเชื่อมโยงแบบไดนามิกบางครั้งดูเหมือนเป็น PITA ที่สำคัญสำหรับผู้ที่ใช้ Linux เมื่อติดตั้งโปรแกรม X (อาจเขียนด้วย C ++) หมายความว่าต้องติดตามและติดตั้งเวอร์ชันเฉพาะ ของไลบรารีที่คุณมีเวอร์ชันต่างๆในระบบของคุณอยู่แล้ว
jamesqf

@jamesqf ใช่ Unix ไม่มีการเชื่อมโยงแบบไดนามิกก่อน Windows ฉันรู้น้อยมากเกี่ยวกับการเชื่อมโยงแบบไดนามิกใน Unix / Linux แต่ดูเหมือนว่าจะไม่ราบรื่นเท่าที่ควรในระบบปฏิบัติการโดยทั่วไป
user34660

12

ฉันจะเพิ่มคำตอบอีกหนึ่งคำตอบเพื่อจัดการกับการสนทนาเชิงสัมผัสที่เกิดขึ้น

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

ในทางตรงกันข้าม Pascal ABI ดั้งเดิมผลักอาร์กิวเมนต์จากซ้ายไปขวาและ callee จะต้องแสดงอาร์กิวเมนต์ C ABI ดั้งเดิมเหนือกว่า Pascal ABI ดั้งเดิมในสองจุดสำคัญ ลำดับการพุชอาร์กิวเมนต์หมายความว่าทราบค่าออฟเซ็ตสแต็กของอาร์กิวเมนต์แรกเสมอโดยอนุญาตให้ฟังก์ชันที่มีอาร์กิวเมนต์ไม่ทราบจำนวนโดยที่อาร์กิวเมนต์แรกจะควบคุมจำนวนอาร์กิวเมนต์อื่น ๆ (ala printf)

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

Windows 3.1 ABI ดั้งเดิมนั้นใช้ภาษาปาสคาล ด้วยเหตุนี้จึงใช้ Pascal ABI (อาร์กิวเมนต์เรียงลำดับจากซ้ายไปขวา callee pops) เนื่องจากหมายเลขอาร์กิวเมนต์ที่ไม่ตรงกันอาจนำไปสู่ความเสียหายของสแต็กจึงเกิดโครงการโกงกิน ชื่อฟังก์ชั่นแต่ละชื่อถูกทำให้ยุ่งเหยิงด้วยตัวเลขที่ระบุขนาดของอาร์กิวเมนต์เป็นไบต์ ดังนั้นบนเครื่อง 16 บิตฟังก์ชันต่อไปนี้ (ไวยากรณ์ C):

int function(int a)

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

Windows 32 บิตเป็นต้นไปให้ใช้stdcallABI แทน คล้ายกับ Pascal ABI ยกเว้นคำสั่งกดจะเหมือนใน C จากขวาไปซ้าย เช่นเดียวกับ Pascal ABI ชื่อที่ยุ่งเหยิงจะเปลี่ยนขนาดของอาร์กิวเมนต์ไบต์ในชื่อฟังก์ชันเพื่อหลีกเลี่ยงความเสียหายของสแต็ก

ซึ่งแตกต่างจากการอ้างสิทธิ์ที่อื่นที่นี่ C ABI จะไม่ยุ่งเกี่ยวกับชื่อฟังก์ชันแม้แต่ใน Visual Studio ในทางกลับกันฟังก์ชั่นการดัดแปลงที่ตกแต่งด้วยstdcallข้อกำหนด ABI นั้นไม่ซ้ำกับ VS GCC ยังรองรับ ABI นี้แม้ว่าจะคอมไพล์สำหรับ Linux ก็ตาม สิ่งนี้ถูกใช้อย่างกว้างขวางโดยWineซึ่งใช้ตัวโหลดของตัวเองเพื่อให้สามารถเชื่อมโยงเวลาทำงานของไบนารีที่คอมไพล์ของ Linux กับ DLL ที่คอมไพล์ของ Windows


9

คอมไพเลอร์ C ++ ใช้ชื่อ mangling เพื่ออนุญาตให้ใช้ชื่อสัญลักษณ์เฉพาะสำหรับฟังก์ชันที่โอเวอร์โหลดซึ่งลายเซ็นจะเหมือนกัน โดยทั่วไปจะเข้ารหัสประเภทของอาร์กิวเมนต์เช่นกันซึ่งช่วยให้เกิดความหลากหลายในระดับตามฟังก์ชัน

C ไม่ต้องการสิ่งนี้เนื่องจากไม่อนุญาตให้มีการทำงานมากเกินไป

โปรดทราบว่าชื่อที่สับสนเป็นเหตุผลหนึ่ง (แต่ไม่ใช่อย่างเดียว!) ที่ไม่สามารถพึ่งพา 'C ++ ABI' ได้


8

C ++ ต้องการที่จะสามารถทำงานร่วมกับรหัส C ที่เชื่อมโยงกับมันหรือเชื่อมโยงกับ

C คาดหวังชื่อฟังก์ชันที่ไม่มีการเปลี่ยนแปลงชื่อ

หาก C ++ ทำให้มันยุ่งเหยิงมันจะไม่พบฟังก์ชันที่ไม่ได้ถูกส่งออกจาก C หรือ C จะไม่พบฟังก์ชัน C ++ ที่ส่งออก ตัวเชื่อม C ต้องได้รับชื่อที่ตัวเองคาดหวังเพราะไม่รู้ว่ามันมาจากหรือไปที่ C ++


3

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

ฉันเสนอโครงร่างสำหรับการเชื่อมโยง type-safe ของตัวแปร C และฟังก์ชันผ่านการใช้ mangling ย้อนกลับไปในปี 1991 โครงร่างนี้ไม่เคยถูกนำมาใช้เพราะอย่างที่อื่น ๆ ได้กล่าวไว้ที่นี่สิ่งนี้จะทำลายความเข้ากันได้ย้อนหลัง


1
คุณหมายถึง "อนุญาตให้ตรวจสอบประเภทได้ในเวลาลิงก์ " ประเภทจะถูกตรวจสอบในเวลาคอมไพล์ แต่การเชื่อมโยงกับชื่อที่ไม่เชื่อมโยงไม่สามารถตรวจสอบได้ว่าการประกาศที่ใช้ในหน่วยคอมไพล์ต่างกันนั้นเห็นด้วยหรือไม่ และหากพวกเขาไม่เห็นด้วยระบบการสร้างของคุณจะเสียโดยพื้นฐานและจำเป็นต้องได้รับการแก้ไข
cmaster - คืนสถานะโมนิกา
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.