คุณลักษณะบางอย่างของภาษาซีเริ่มต้นจากการแฮ็กซึ่งเพิ่งเกิดขึ้นได้
ลายเซ็นหลายรายการสำหรับรายการอาร์กิวเมนต์หลักและความยาวผันแปรเป็นหนึ่งในคุณสมบัติเหล่านั้น
โปรแกรมเมอร์สังเกตว่าพวกเขาสามารถส่งผ่านอาร์กิวเมนต์พิเศษไปยังฟังก์ชันได้และไม่มีอะไรเลวร้ายเกิดขึ้นกับคอมไพเลอร์ที่กำหนด
เป็นกรณีนี้หากรูปแบบการโทรเป็นเช่นนั้น:
- ฟังก์ชันการโทรทำความสะอาดอาร์กิวเมนต์
- อาร์กิวเมนต์ซ้ายสุดอยู่ใกล้กับด้านบนสุดของสแต็กหรืออยู่ใกล้กับฐานของสแต็กเฟรมดังนั้นอาร์กิวเมนต์ปลอมจะไม่ทำให้การกำหนดแอดเดรสเป็นโมฆะ
รูปแบบการเรียกชุดหนึ่งที่ปฏิบัติตามกฎเหล่านี้คือพารามิเตอร์แบบสแต็กที่ส่งผ่านโดยที่ผู้เรียกจะแสดงอาร์กิวเมนต์และจะถูกผลักจากขวาไปซ้าย:
;; pseudo-assembly-language
;; main(argc, argv, envp); call
push envp ;; rightmost argument
push argv ;;
push argc ;; leftmost argument ends up on top of stack
call main
pop ;; caller cleans up
pop
pop
ในคอมไพเลอร์ซึ่งเป็นกรณีของการเรียกแบบนี้ไม่จำเป็นต้องทำอะไรเป็นพิเศษเพื่อรองรับทั้งสองชนิดmain
หรือแม้แต่ชนิดเพิ่มเติม main
สามารถเป็นฟังก์ชันที่ไม่มีอาร์กิวเมนต์ซึ่งในกรณีนี้จะถูกลบเลือนไปยังรายการที่ถูกผลักลงบนสแต็ก หากเป็นฟังก์ชันของสองอาร์กิวเมนต์ก็จะพบargc
และargv
เป็นสองรายการกองซ้อนบนสุด หากเป็นตัวแปรสามอาร์กิวเมนต์เฉพาะแพลตฟอร์มที่มีตัวชี้สภาพแวดล้อม (ส่วนขยายทั่วไป) ก็จะใช้ได้เช่นกัน: จะพบว่าอาร์กิวเมนต์ที่สามนั้นเป็นองค์ประกอบที่สามจากด้านบนของสแต็ก
ดังนั้นการโทรแบบคงที่จึงใช้ได้กับทุกกรณีโดยอนุญาตให้มีการเชื่อมโยงโมดูลเริ่มต้นระบบคงที่เดียวกับโปรแกรม โมดูลนั้นสามารถเขียนด้วยภาษา C เป็นฟังก์ชันที่คล้ายกับสิ่งนี้:
extern int main(int argc, char **argv, char **envp);
void __start(void)
{
exit(main(argc_from_somewhere, argv_from_somewhere, envp_from_somewhere));
}
กล่าวอีกนัยหนึ่งโมดูลเริ่มต้นนี้จะเรียกใช้อาร์กิวเมนต์หลักสามตัวเสมอ หาก main ไม่มีข้อโต้แย้งใด ๆ หรือเพียงอย่างเดียวint, char **
มันก็ทำงานได้ดีเช่นเดียวกับว่ามันไม่มีข้อโต้แย้งใด ๆ เนื่องจากหลักการเรียก
หากคุณต้องทำสิ่งนี้ในโปรแกรมของคุณสิ่งนี้จะไม่สามารถรายงานได้และถือว่าเป็นพฤติกรรมที่ไม่ได้กำหนดโดย ISO C: การประกาศและเรียกใช้ฟังก์ชันในลักษณะเดียวและกำหนดในอีกลักษณะหนึ่ง แต่เคล็ดลับการเริ่มต้นของคอมไพเลอร์ไม่จำเป็นต้องพกพาได้ ไม่ได้รับคำแนะนำจากกฎสำหรับโปรแกรมพกพา
แต่สมมติว่ารูปแบบการเรียกนั้นไม่สามารถทำงานในลักษณะนี้ได้ ในกรณีนั้นคอมไพเลอร์ต้องปฏิบัติmain
เป็นพิเศษ เมื่อสังเกตเห็นว่ากำลังรวบรวมmain
ฟังก์ชันก็สามารถสร้างรหัสที่เข้ากันได้กับการเรียกใช้อาร์กิวเมนต์สามตัว
กล่าวคือคุณเขียนสิ่งนี้:
int main(void)
{
}
แต่เมื่อคอมไพเลอร์เห็นมันจะทำการแปลงรหัสเป็นหลักเพื่อให้ฟังก์ชันที่คอมไพล์มีลักษณะดังนี้:
int main(int __argc_ignore, char **__argv_ignore, char **__envp_ignore)
{
}
ยกเว้นว่าชื่อ__argc_ignore
นั้นไม่มีอยู่จริง ไม่มีการนำชื่อดังกล่าวเข้าสู่ขอบเขตของคุณและจะไม่มีคำเตือนใด ๆ เกี่ยวกับอาร์กิวเมนต์ที่ไม่ได้ใช้ การแปลงรหัสทำให้คอมไพลเลอร์ส่งโค้ดด้วยการเชื่อมโยงที่ถูกต้องซึ่งรู้ว่าต้องล้างอาร์กิวเมนต์สามตัว
กลยุทธ์การนำไปใช้งานอื่นสำหรับคอมไพเลอร์หรืออาจเป็นตัวเชื่อมโยงเพื่อสร้าง__start
ฟังก์ชันแบบกำหนดเอง(หรืออะไรก็ตามที่เรียกว่า) หรืออย่างน้อยก็เลือกหนึ่งจากทางเลือกที่รวบรวมไว้ล่วงหน้าหลายรายการ ข้อมูลอาจถูกเก็บไว้ในอ็อบเจ็กต์ไฟล์เกี่ยวกับรูปแบบที่รองรับที่main
กำลังใช้อยู่ ผู้เชื่อมโยงสามารถดูข้อมูลนี้และเลือกเวอร์ชันที่ถูกต้องของโมดูลเริ่มต้นซึ่งมีการเรียกmain
ที่เข้ากันได้กับคำจำกัดความของโปรแกรม การใช้งาน C มักจะมีรูปแบบที่รองรับเพียงเล็กน้อยเท่านั้นmain
ดังนั้นแนวทางนี้จึงเป็นไปได้
คอมไพเลอร์สำหรับภาษา C99 มักจะต้องปฏิบัติmain
เป็นพิเศษในระดับหนึ่งเสมอเพื่อสนับสนุนการแฮ็กที่หากฟังก์ชันสิ้นสุดลงโดยไม่มีreturn
คำสั่งพฤติกรรมจะเหมือนกับว่าreturn 0
ถูกดำเนินการ สิ่งนี้สามารถรักษาได้อีกครั้งโดยการแปลงรหัส คอมไพเลอร์สังเกตว่าฟังก์ชันที่เรียกว่าmain
กำลังถูกคอมไพล์ จากนั้นจะตรวจสอบว่าส่วนท้ายของร่างกายสามารถเข้าถึงได้หรือไม่ ถ้าเป็นเช่นนั้นมันจะแทรกไฟล์return 0;
main
วิธีเดียวในโปรแกรมเดียวในC
(หรือในภาษาใดก็ได้ที่มีโครงสร้างดังกล่าว)