การใช้ _start () ใน C คืออะไร?


126

ฉันเรียนรู้จากเพื่อนร่วมงานว่าสามารถเขียนและรันโปรแกรม C ได้โดยไม่ต้องเขียนmain()ฟังก์ชัน สามารถทำได้ดังนี้:

my_main.c

/* Compile this with gcc -nostartfiles */

#include <stdlib.h>

void _start() {
  int ret = my_main();
  exit(ret); 
}

int my_main() {
  puts("This is a program without a main() function!");
  return 0; 
}

รวบรวมด้วยคำสั่งนี้:

gcc -o my_main my_main.c nostartfiles

เรียกใช้ด้วยคำสั่งนี้:

./my_main

เมื่อไหร่ที่ต้องทำแบบนี้? มีสถานการณ์จริงในโลกที่จะเป็นประโยชน์หรือไม่?


1
ที่เกี่ยวข้องจากระยะไกล: stackoverflow.com/questions/2548486/compiling-without-libc
Mohit Jain

7
บทความคลาสสิกที่แสดงให้เห็นบางส่วนของการทำงานภายในของวิธีการที่โปรแกรมเริ่มต้นขึ้น: ลมกรดสอนในการสร้างจริงๆ teensy ELF Executables สำหรับลินุกซ์ นี้เป็นที่ดีอ่านกล่าวถึงว่าบางส่วนของจุดปลีกย่อยของและภายนอกของสิ่งอื่น_start()main()

1
ภาษา C นั้นไม่ได้พูดอะไรเกี่ยวกับ_startหรือเกี่ยวกับจุดเข้าใช้งานอื่นใดนอกจากmain(ยกเว้นว่าชื่อของจุดเริ่มต้นนั้นถูกกำหนดให้ใช้งานสำหรับการใช้งานอิสระ (ฝังตัว))
Keith Thompson

คำตอบ:


108

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

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


2
อีกตัวอย่างหนึ่งคือตัวเชื่อมโยง / ตัวโหลดแบบไดนามิกของ Linux ซึ่งมี _start กำหนดไว้เอง
PP

2
@BlueMoon แต่นั่น_startมาจาก object file crt0.oด้วย
fuz

2
@ThomasMatthews มาตรฐานไม่ได้ระบุ_start; ในความเป็นจริงมันไม่ได้ระบุว่าเกิดอะไรขึ้นก่อนที่จะmainถูกเรียกเลยเพียง แต่ระบุว่าจะต้องปฏิบัติตามเงื่อนไขใดเมื่อmainถูกเรียก มันเป็นแบบแผนมากกว่าสำหรับจุดเริ่มต้นที่จะ_startย้อนกลับไปในวันเก่า
fuz

1
"การใช้งานอ้างอิงของภาษาโปรแกรม Go ทำเช่นนั้นเนื่องจากพวกเขาต้องการโมเดลเธรดที่ไม่ได้มาตรฐาน" crt0.o คือ C เฉพาะ (crt-> C runtime) ไม่มีเหตุผลที่จะคาดว่าจะใช้กับภาษาอื่น และโมเดลเธรดของ Go เป็นไปตามมาตรฐานอย่างสมบูรณ์
Steve Cox

8
@SteveCox ภาษาโปรแกรมจำนวนมากถูกสร้างขึ้นบนรันไทม์ C เนื่องจากง่ายต่อการใช้ภาษาด้วยวิธีนี้ Go ไม่ใช้รูปแบบการทำเกลียวแบบปกติ พวกเขาใช้กองซ้อนขนาดเล็กที่จัดสรรแบบฮีปและตัวกำหนดตารางเวลาของตนเอง นี่ไม่ใช่รุ่นเธรดมาตรฐานอย่างแน่นอน
fuz

45

แม้ว่าmainจะเป็นจุดเริ่มต้นสำหรับโปรแกรมของคุณจากมุมมองของโปรแกรมเมอร์ แต่_startเป็นจุดเริ่มต้นตามปกติจากมุมมองของระบบปฏิบัติการ (คำสั่งแรกที่ดำเนินการหลังจากโปรแกรมของคุณเริ่มต้นจากระบบปฏิบัติการ)

ในโปรแกรม C ทั่วไปและโดยเฉพาะ C ++ มีการทำงานจำนวนมากก่อนที่การดำเนินการจะเข้าสู่หลัก โดยเฉพาะอย่างยิ่งสิ่งต่างๆเช่นการเริ่มต้นตัวแปรส่วนกลาง ที่นี่คุณจะพบคำอธิบายที่ดีเกี่ยวกับทุกสิ่งที่เกิดขึ้นระหว่าง_start()และmain()และหลังจากที่ main ออกอีกครั้ง (ดูความคิดเห็นด้านล่าง)
รหัสที่จำเป็นสำหรับสิ่งนั้นมักจะมาจากผู้เขียนคอมไพเลอร์ในไฟล์เริ่มต้น แต่ด้วยการตั้งค่าสถานะที่–nostartfilesคุณบอกคอมไพเลอร์เป็นหลัก: "อย่ากังวลว่าจะให้ไฟล์เริ่มต้นมาตรฐานแก่ฉันให้ฉันควบคุมสิ่งที่เกิดขึ้นจาก เริ่มต้น"

บางครั้งสิ่งนี้จำเป็นและมักใช้กับระบบฝังตัว เช่นหากคุณไม่มีระบบปฏิบัติการและคุณต้องเปิดใช้งานบางส่วนของระบบหน่วยความจำของคุณด้วยตนเอง (เช่นแคช) ก่อนที่จะเริ่มต้นอ็อบเจ็กต์ส่วนกลางของคุณ


global vars เป็นส่วนหนึ่งของส่วนข้อมูลดังนั้นจึงมีการตั้งค่าระหว่างการโหลดโปรแกรม (หากเป็น const จะเป็นส่วนหนึ่งของส่วนข้อความเรื่องราวเดียวกัน) ฟังก์ชัน _start ไม่เกี่ยวข้องกับสิ่งนั้นโดยสิ้นเชิง
Cheiron

@Cheiron: ขออภัย emistake ของฉันใน c ++ ตัวแปรส่วนกลางมักจะเริ่มต้นโดยตัวสร้างซึ่งทำงานอยู่ภายใน_start()(หรือจริงๆแล้วฟังก์ชันอื่นเรียกโดยมัน) และใน Bare-Metal-Programs จำนวนมากคุณคัดลอกข้อมูลทั่วโลกทั้งหมดจากแฟลชไปยัง RAM อย่างชัดเจน อย่างแรกซึ่งก็เกิดขึ้นเช่น_start()กัน แต่คำถามนี้ไม่เกี่ยวกับ c ++ หรือโค้ด bare-metal
MikeMB

1
โปรดทราบว่าในโปรแกรมที่จัดหาของตัวเอง_startไลบรารี C จะไม่ได้รับการเตรียมใช้งานเว้นแต่คุณจะทำตามขั้นตอนพิเศษด้วยตัวเอง - อาจไม่ปลอดภัยที่จะใช้ฟังก์ชันที่ไม่ปลอดภัยจากสัญญาณ async จากโปรแกรมดังกล่าว (ไม่มีการรับประกันอย่างเป็นทางการว่าฟังก์ชั่นไลบรารีใด ๆจะทำงานได้ แต่ฟังก์ชัน async-signal-safe ไม่สามารถอ้างถึงข้อมูลส่วนกลางใด ๆ ได้เลยดังนั้นพวกเขาจึงต้องออกนอกเส้นทางเพื่อทำงานผิดพลาด)
zwol

@zwol ถูกต้องเพียงบางส่วน ตัวอย่างเช่นฟังก์ชันดังกล่าวอาจจัดสรรหน่วยความจำ การจัดสรรหน่วยความจำเป็นปัญหาเมื่อโครงสร้างข้อมูลภายในสำหรับmallocไม่ได้เตรียมใช้งาน
fuz

1
@FUZxxl ต้องบอกว่าผมสังเกตเห็นว่าฟังก์ชั่น async สัญญาณปลอดภัยได้รับการอนุญาตให้แก้ไขerrno(เช่นreadและwriteมี async สัญญาณที่ปลอดภัยและสามารถตั้งค่าerrno) และที่น่ากลัวจะเป็นปัญหาขึ้นอยู่กับว่าเมื่อต่อด้ายerrnoที่ตั้งจะถูกจัดสรร .
zwol

2

นี่คือภาพรวมที่ดีของสิ่งที่เกิดขึ้นระหว่างการเริ่มต้นโปรแกรมก่อนหน้า mainนี้ โดยเฉพาะอย่างยิ่งมันแสดงให้เห็นว่า__startเป็นจุดเริ่มต้นที่แท้จริงของโปรแกรมของคุณจากมุมมอง OS

เป็นที่อยู่แรกสุดที่ตัวชี้คำสั่งจะเริ่มนับในโปรแกรมของคุณ

รหัสที่นั่นเรียกใช้รูทีนไลบรารีรันไทม์ C เพียงเพื่อทำความสะอาดบางอย่างจากนั้นโทรหาคุณmainจากนั้นนำสิ่งต่าง ๆ ลงมาและเรียกexitด้วยรหัสออกที่mainส่งคืน


ภาพที่มีค่าพันคำ:

C แผนภาพเริ่มต้นรันไทม์


ป.ล. : คำตอบนี้ได้รับการปลูกถ่ายจากคำถามอื่นซึ่ง SO ได้ปิดอย่างเป็นประโยชน์ว่าซ้ำกับคำถามนี้


โพสต์ข้ามเพื่อรักษาการวิเคราะห์ที่ยอดเยี่ยมและภาพที่สวยงาม
ulidtko

1

เมื่อไหร่ที่ต้องทำแบบนี้?

เมื่อคุณต้องการรหัสเริ่มต้นของคุณเองสำหรับโปรแกรมของคุณ

main ไม่ใช่รายการแรกสำหรับโปรแกรม C _startเป็นรายการแรกหลังม่าน

ตัวอย่างใน Linux:

_start: # _start is the entry point known to the linker
    xor %ebp, %ebp            # effectively RBP := 0, mark the end of stack frames
    mov (%rsp), %edi          # get argc from the stack (implicitly zero-extended to 64-bit)
    lea 8(%rsp), %rsi         # take the address of argv from the stack
    lea 16(%rsp,%rdi,8), %rdx # take the address of envp from the stack
    xor %eax, %eax            # per ABI and compatibility with icc
    call main                 # %edi, %rsi, %rdx are the three args (of which first two are C standard) to main

    mov %eax, %edi    # transfer the return of main to the first argument of _exit
    xor %eax, %eax    # per ABI and compatibility with icc
    call _exit        # terminate the program

มีสถานการณ์จริงในโลกที่จะเป็นประโยชน์หรือไม่?

หากคุณหมายถึงใช้ของเราเอง_start:

ใช่ในซอฟต์แวร์ฝังตัวเชิงพาณิชย์ส่วนใหญ่ที่ฉันเคยทำงานด้วยเราจำเป็นต้องใช้งานของเราเอง_startเกี่ยวกับความต้องการหน่วยความจำและประสิทธิภาพเฉพาะของเรา

หากคุณหมายถึงให้วางmainฟังก์ชันและเปลี่ยนเป็นอย่างอื่น:

ไม่ฉันไม่เห็นประโยชน์ใด ๆ ที่ทำเช่นนั้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.