รหัส C ที่สับสนนี้อ้างว่าทำงานโดยไม่มี main () แต่มันทำอะไรได้จริง?


84

นี่เรียกทางอ้อมmainหรือเปล่า? ยังไง


146
มาโครที่กำหนดขยายเริ่มพูดว่า "หลัก" มันเป็นเพียงกลลวง ไม่มีอะไรน่าสนใจ.
rghome

10
toolchain ของคุณควรมีตัวเลือกในการปล่อยโค้ดที่ประมวลผลไว้แล้วไว้ในไฟล์ - ไฟล์จริงที่คอมไพล์ - ซึ่งคุณจะเห็นมันมี main ()

@rghome ทำไมไม่โพสเป็นคำตอบ และมันน่าสนใจอย่างชัดเจนเมื่อพิจารณาจากจำนวนโหวตที่เพิ่มขึ้น
Matsemann

3
@Matsemann ว้าว! ฉันไม่ได้สังเกตเห็นการโหวตที่เพิ่มขึ้น ฉันสามารถเปลี่ยนเป็นคำตอบได้และหากความคิดเห็นที่โหวตขึ้นเป็นคำตอบก็จะเป็นคะแนนที่ดีที่สุดของฉัน แต่มีคำตอบโดยละเอียดอยู่แล้ว ฉันคิดว่าประเด็นของความคิดเห็นของฉันคือมันไม่น่าสนใจจริงๆดังนั้นมันจึงเป็นทางเลือกสำหรับคนที่ไม่ต้องการโหวตคำตอบ ขอบคุณที่ชี้ให้เห็น
rghome

พวกมันขึ้นอยู่กับตัวเชื่อมโยงเป็นเครื่องมือระบบปฏิบัติการในการกำหนดจุดเริ่มต้นไม่ใช่ภาษาตัวเอง คุณสามารถกำหนดจุดเข้าใช้งานของเราเองและคุณสามารถสร้างไลบรารีที่เรียกใช้งานได้ด้วย! unix.stackexchange.com/a/223415/37799
Ho1

คำตอบ:


194

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

ในกรณีของคุณmainถูกซ่อนไว้โดยคำจำกัดความของตัวประมวลผลล่วงหน้า begin()จะขยายไป ซึ่งต่อไปจะขยายไปยัง decode(a,n,i,m,a,t,e)main


decode(s,t,u,m,p,e,d)เป็นมาโครที่กำหนดพารามิเตอร์ที่มีพารามิเตอร์ 7 ตัว m##s##u##tรายการทดแทนสำหรับแมโครนี้คือ m, s, uและtเป็นพารามิเตอร์4 th , 1 st , 3 rdและ 2 nd ที่ใช้ในรายการแทนที่

ส่วนที่เหลือไม่มีประโยชน์ ( เพียงเพื่อทำให้สับสน ) อาร์กิวเมนต์ที่ส่งไปยังdecodeคือ " a , n , i , m , a, t, e" ดังนั้นตัวระบุm, s, uและtจะถูกแทนที่ด้วยอาร์กิวเมนต์m, a, iและnตามลำดับ


11
@GrijeshChauhan คอมไพเลอร์ C ทั้งหมดประมวลผลมาโครซึ่งเป็นสิ่งจำเป็นตามมาตรฐาน C ทั้งหมดตั้งแต่ C89
jdarthenay

17
นั่นผิดอย่างชัดเจน บน Linux ฉันสามารถ_start()ใช้ได้ หรือระดับต่ำกว่านั้นฉันสามารถลองปรับตำแหน่งเริ่มต้นของโปรแกรมของฉันให้ตรงกับที่อยู่ที่ตั้งค่า IP ไว้หลังจากบูต main()คือ C มาตรฐานห้องสมุด C เองไม่ได้กำหนดข้อ จำกัด ในเรื่องนี้
ljrk

1
@haccks ไลบรารีมาตรฐานกำหนดจุดเข้าใช้งาน ภาษาตัวเองไม่สนใจ
ljrk

3
คุณช่วยอธิบายได้ไหมว่าdecode(a,n,i,m,a,t,e)กลายเป็นm##a##i##nอย่างไร? มันแทนที่อักขระ? คุณสามารถให้ลิงค์ไปยังเอกสารของdecodeฟังก์ชันได้หรือไม่? ขอบคุณ.
AL

1
@AL First beginถูกกำหนดให้แทนที่โดยdecode(a,n,i,m,a,t,e)ที่กำหนดไว้ก่อนหน้า ฟังก์ชันนี้รับอาร์กิวเมนต์s,t,u,m,p,e,dและเชื่อมต่อเข้าด้วยกันในรูปแบบนี้m##s##u##t( ##หมายถึงการเรียงต่อกัน) กล่าวคือไม่สนใจค่าของ p, e และ d ในขณะที่คุณ "เรียกว่า" decodeกับ s = A, t = n, U = i, m = ม. ได้อย่างมีประสิทธิภาพแทนที่ด้วยbegin main
ljrk

71

ลองใช้gcc -E source.cผลลัพธ์ลงท้ายด้วย:

ดังนั้นmain()ฟังก์ชันจึงถูกสร้างขึ้นโดยตัวประมวลผลล่วงหน้า


37

โปรแกรมในคำถามไม่โทรmain()เนื่องจากการขยายตัวแมโคร แต่สมมติฐานของคุณเป็นข้อบกพร่อง - มันไม่ได้มีการเรียกร้องmain()ที่ทุกคน!

พูดอย่างเคร่งครัดคุณสามารถมีโปรแกรม C และสามารถรวบรวมได้โดยไม่ต้องมีmainสัญลักษณ์ mainเป็นสิ่งที่c libraryคาดว่าจะกระโดดเข้ามาหลังจากเสร็จสิ้นการเริ่มต้นของตัวเอง โดยปกติคุณจะกระโดดเข้ามาmainจากสัญลักษณ์ libc ที่เรียกว่า_start. เป็นไปได้เสมอที่จะมีโปรแกรมที่ถูกต้องซึ่งดำเนินการแอสเซมบลีโดยไม่ต้องมี main ดูที่นี้:

รวบรวมข้อมูลข้างต้นด้วยgcc -nostdlib without_main.cและดูว่ากำลังพิมพ์Hello World!บนหน้าจอเพียงแค่เรียกระบบ (ขัดจังหวะ) ในชุดประกอบแบบอินไลน์

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับปัญหานี้โปรดดูที่ไฟล์ บล็อก ksplice

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

ค่าในอาร์เรย์เป็นไบต์ที่สอดคล้องกับคำสั่งที่จำเป็นในการพิมพ์ Hello World บนหน้าจอ หากต้องการทราบรายละเอียดเพิ่มเติมเกี่ยวกับวิธีการทำงานของโปรแกรมนี้โปรดดูที่บล็อกโพสต์ซึ่งเป็นที่ที่ฉันอ่านก่อน

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


1
เป็นชื่อของ_startส่วนหนึ่งของมาตรฐานที่กำหนดไว้หรือเป็นเพียงการนำไปใช้งานโดยเฉพาะ? แน่นอนว่า "main as an array" ของคุณเป็นสถาปัตยกรรมเฉพาะ สิ่งสำคัญอีกอย่างคือเคล็ดลับ "main as an array" ของคุณจะล้มเหลวในขณะทำงานเนื่องจากข้อ จำกัด ด้านความปลอดภัย (แม้ว่าจะมีโอกาสมากกว่าหากคุณไม่ได้ใช้constQualifier และยังมีระบบอีกหลายระบบที่อนุญาต)
mAh

1
@mah: _startไม่ได้อยู่ในมาตรฐานเอลฟ์ แต่ AMD64 psABI ประกอบด้วยการอ้างอิงไป_startที่3.4 กระบวนการเริ่มต้น อย่างเป็นทางการ ELF รู้เฉพาะเกี่ยวกับที่อยู่e_entryในส่วนหัวของ ELF _startเป็นเพียงชื่อที่เลือกใช้งาน
ninjalj

1
@mah สิ่งสำคัญเช่นกันมันจะไม่มีเหตุผลที่เคล็ดลับ "main as an array" ของคุณจะล้มเหลวในขณะทำงานเนื่องจากข้อ จำกัด ด้านความปลอดภัย (แม้ว่าจะมีโอกาสมากขึ้นหากคุณไม่ได้ใช้คุณสมบัติ const และระบบยังคงอนุญาต มัน). เฉพาะในกรณีที่ไฟล์ปฏิบัติการขั้นสุดท้ายสามารถแยกแยะได้ว่าเป็นสิ่งที่ไม่ปลอดภัยเท่านั้น - ไบนารีที่เรียกใช้งานได้คือไฟล์ปฏิบัติการแบบไบนารีไม่ว่ามันจะไปที่นั่นได้อย่างไร และconstจะไม่สำคัญหนึ่งบิต - mainชื่อสัญลักษณ์ในแฟ้มที่ปฏิบัติการที่ไบนารี ไม่มากไม่น้อย. constเป็นโครงสร้าง C ที่ไม่มีความหมายในเวลาดำเนินการ
Andrew Henle

1
@Stewart: มันล้มเหลวอย่างแน่นอนบน ARMv6l (ความผิดพลาดในการแบ่งส่วน) แต่ควรใช้กับสถาปัตยกรรม x86-64 ใดก็ได้
leftaround ประมาณ

@AndrewHenle ไบนารีที่เรียกใช้งานได้คือไบนารีปฏิบัติการไม่ว่ามันจะไปที่นั่นได้อย่างไร - ไม่จริงอย่างแน่นอน ไบนารีไฟล์ปฏิบัติการไม่ได้เป็นเพียงหยดเดียวของคำสั่งปฏิบัติการ แต่เป็นหยดของพาร์ติชันที่แมปอย่างระมัดระวังซึ่งบางส่วนเป็นคำแนะนำซึ่งบางส่วนเป็นข้อมูลแบบอ่านอย่างเดียวและบางส่วนเป็นข้อมูลที่จะเริ่มต้นเป็นข้อมูลแบบอ่าน - เขียน MMU ของฮาร์ดแวร์ความปลอดภัย (บางตัว) สามารถป้องกันการเรียกใช้งานจากหน้าที่ไม่ได้ทำเครื่องหมายไว้เช่นนี้และนี่เป็นคุณสมบัติที่ดีในการป้องกันตัวอย่างเช่นสแต็กล้นที่นำไปสู่การเรียกใช้โค้ดบนสแต็ก แต่น่าเศร้าที่บางครั้งก็ถูกต้องหรือไม่ได้เปิดใช้งานบ่อยครั้ง
mAh

30

มีคนพยายามทำตัวเหมือนนักเวทย์ เขาคิดว่าเขาหลอกเราได้ แต่เราทุกคนรู้ว่าการเรียกใช้โปรแกรม c เริ่มต้นด้วยmain().

int begin()จะถูกแทนที่ด้วยdecode(a,n,i,m,a,t,e)โดยหนึ่งผ่านเวที preprocessor จากนั้นอีกครั้งdecode(a,n,i,m,a,t,e)จะถูกแทนที่ด้วย m ## a ## i ## n ในฐานะที่เป็นโดยสมาคมตำแหน่งของสายแมโคร ประสงค์มีค่าของตัวละครs aในทำนองเดียวกันuจะถูกแทนที่ด้วย 'i' และtจะถูกแทนที่ด้วย 'n' และนั่นคือวิธีที่m##s##u##tจะกลายเป็นmain

เกี่ยวกับ##สัญลักษณ์ในการขยายมาโครเป็นตัวดำเนินการก่อนการประมวลผลและทำการวางโทเค็น เมื่อขยายแมโครโทเค็นทั้งสองที่อยู่ด้านใดด้านหนึ่งของตัวดำเนินการ '##' แต่ละตัวจะรวมกันเป็นโทเค็นเดียวซึ่งจะแทนที่โทเค็น '##' และโทเค็นดั้งเดิมสองตัวในการขยายมาโคร

หากคุณไม่เชื่อฉันคุณสามารถรวบรวมรหัสของคุณด้วย-Eแฟล็ก จะหยุดกระบวนการรวบรวมหลังจากประมวลผลล่วงหน้าและคุณสามารถเห็นผลลัพธ์ของการวางโทเค็น


11

decode(a,b,c,d,[...])shuffles dacbสี่ข้อโต้แย้งแรกและร่วมกับพวกเขาที่จะได้รับการระบุตัวตนใหม่ในการสั่งซื้อ (ที่เหลืออีกสามข้อโต้แย้งจะถูกละเว้น.) ยกตัวอย่างเช่นให้ระบุdecode(a,n,i,m,[...]) mainโปรดทราบว่านี่คือสิ่งที่beginมาโครกำหนดให้เป็น

ดังนั้นbeginมาโครจึงถูกกำหนดให้เป็นmainไฟล์.


2

ในตัวอย่างของคุณมีmain()ฟังก์ชันอยู่เนื่องจากbeginเป็นมาโครที่คอมไพลเลอร์แทนที่ด้วยdecodeมาโครซึ่งจะแทนที่ด้วยนิพจน์ m ## s ## u ## t การใช้การขยายตัวแมโคร##คุณจะเข้าถึงคำจากmain decodeนี่คือร่องรอย:

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

ใน Windows คุณไม่เคยใช้main()แต่ค่อนข้างWinMainหรือwWinMainแม้ว่าคุณสามารถใช้main()แม้จะมี toolchain ใน Linux คุณสามารถใช้_startไฟล์.

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


@vaxquis คุณพูดถูก แต่นี่เป็นคำตอบบางส่วนที่ฉันเขียนเพื่อชมเชย / แก้ไขคำตอบแรกที่ผูกmain()ฟังก์ชันกับภาษาโปรแกรม C ซึ่งไม่ถูกต้อง
Ho1

@vaxquis ฉันคิดว่าการอธิบาย "ฟังก์ชัน main () ไม่จำเป็นในโปรแกรม C" จะเป็นคำตอบบางส่วน ฉันได้เพิ่มย่อหน้าเพื่อให้คำตอบสมบูรณ์ - Ho1 16 นาทีที่แล้ว
Ho1
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.