Apa gunanya _start () di C?

125

Saya belajar dari rekan saya bahwa seseorang dapat menulis dan menjalankan program C tanpa menulis main()fungsi. Itu bisa dilakukan seperti ini:

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; 
}

Kompilasikan dengan perintah ini:

gcc -o my_main my_main.c nostartfiles

Jalankan dengan perintah ini:

./my_main

Kapan seseorang perlu melakukan hal semacam ini? Apakah ada skenario dunia nyata di mana ini akan berguna?

SimpleGuy
sumber
7
Artikel klasik yang mendemonstrasikan beberapa cara kerja bagaimana program dimulai: A Whirlwind Tutorial on Creating Really Teensy ELF Executables untuk Linux . Ini adalah bacaan bagus yang membahas beberapa poin penting dari _start()dan hal-hal lain di luarnya main().
1
Bahasa C itu sendiri tidak mengatakan apa-apa tentang _start, atau tentang titik masuk apa pun selain main(kecuali bahwa nama titik masuk adalah implementasi yang ditentukan untuk implementasi berdiri bebas (tertanam)).
Keith Thompson

Jawaban:

107

Simbol _startadalah titik masuk program Anda. Artinya, alamat simbol itu adalah alamat yang dilompati saat program dimulai. Biasanya, fungsi dengan nama _starttersebut disediakan oleh file bernama crt0.oyang berisi kode startup untuk lingkungan runtime C. Ini mengatur beberapa hal, mengisi array argumen argv, menghitung berapa banyak argumen yang ada, dan kemudian memanggil main. Setelah mainkembali, exitdipanggil.

Jika program tidak ingin menggunakan lingkungan runtime C, program perlu menyediakan kodenya sendiri untuk _start. Misalnya, implementasi referensi dari bahasa pemrograman Go melakukannya karena mereka memerlukan model penguliran non-standar yang memerlukan sihir dengan tumpukan. Ini juga berguna untuk menyediakan sendiri _startketika Anda ingin menulis program atau program yang sangat kecil yang melakukan hal-hal yang tidak biasa.

fuz
sumber
2
Contoh lain adalah linker / loader dinamis Linux yang _startnya telah ditentukan.
PP
2
@BlueMoon Tapi itu _startberasal dari file objek crt0.ojuga.
fuz
2
@Thomasthews Standar tidak menentukan _start; pada kenyataannya, itu tidak menentukan apa yang terjadi sebelum maindipanggil sama sekali, itu hanya menentukan kondisi apa yang harus dipenuhi ketika maindipanggil. Ini lebih merupakan konvensi untuk titik masuk _startyang tanggal kembali ke masa lalu.
fuz
1
"implementasi referensi dari bahasa pemrograman Go melakukannya karena mereka membutuhkan model penguliran non-standar" crt0.o adalah C spesifik (crt-> C runtime). Tidak ada alasan untuk mengharapkannya digunakan untuk bahasa lain. Dan model threading Go sepenuhnya memenuhi standar
Steve Cox
8
@SteveCox Banyak bahasa pemrograman dibangun di atas runtime C karena lebih mudah untuk mengimplementasikan bahasa dengan cara ini. Go tidak menggunakan model penguliran normal. Mereka menggunakan tumpukan kecil dengan alokasi heap dan penjadwal mereka sendiri. Ini tentunya bukan model penguliran standar.
fuz
45

Sementara mainadalah titik masuk untuk program Anda dari perspektif pemrogram, _startadalah titik masuk biasa dari perspektif OS (instruksi pertama yang dijalankan setelah program Anda dimulai dari OS)

Dalam program C dan khususnya C ++, banyak pekerjaan telah dilakukan sebelum eksekusi memasuki main. Terutama hal-hal seperti inisialisasi variabel global. Di sini Anda dapat menemukan penjelasan yang baik tentang segala sesuatu yang terjadi antara _start()dan main()dan juga setelah main telah keluar lagi (lihat komentar di bawah).
Kode yang diperlukan untuk itu biasanya disediakan oleh penulis kompilator dalam file startup, tetapi dengan flag–nostartfiles pada dasarnya Anda memberi tahu kompiler: "Jangan repot-repot memberi saya file startup standar, beri saya kendali penuh atas apa yang terjadi langsung dari Mulailah".

Ini terkadang diperlukan dan sering digunakan pada sistem tertanam. Misalnya jika Anda tidak memiliki OS dan Anda harus secara manual mengaktifkan bagian tertentu dari sistem memori Anda (misalnya cache) sebelum inisialisasi objek global Anda.

MikeMB
sumber
Variabel global adalah bagian dari bagian data dan dengan demikian diatur selama pemuatan program (jika mereka konstan, mereka adalah bagian dari bagian teks, cerita yang sama). Fungsi _start sama sekali tidak terkait dengan itu.
Cheiron
@Cheiron: Maaf, my emistake Di c ++, variabel global sering diinisialisasi oleh konstruktor yang dijalankan di dalam _start()(atau sebenarnya fungsi lain yang disebut olehnya) dan di banyak Bare-Metal-Program, Anda secara eksplisit menyalin semua data global dari flash ke RAM pertama, yang juga terjadi di _start(), tetapi pertanyaan ini bukan tentang c ++ atau kode bare-metal.
MikeMB
1
Perhatikan bahwa dalam program yang memasoknya sendiri _start, pustaka C tidak akan diinisialisasi kecuali Anda mengambil langkah khusus untuk melakukannya sendiri - mungkin tidak aman untuk menggunakan fungsi non-async-signal-safe dari program semacam itu. (Tidak ada jaminan resmi bahwa fungsi perpustakaan apa pun akan berfungsi, tetapi fungsi async-signal-safe tidak dapat merujuk ke data global sama sekali, jadi mereka harus keluar dari jalan menuju kerusakan.)
zwol
@zwol itu hanya sebagian yang benar. Misalnya, fungsi seperti itu mungkin mengalokasikan memori. Mengalokasikan memori bermasalah ketika struktur data internal untuk malloctidak diinisialisasi.
fuz
1
@FUZxxl Karena itu, saya melihat bahwa fungsi async-sinyal-aman yang diperbolehkan untuk memodifikasi errno(misalnya readdan writeyang async-sinyal-aman dan dapat mengatur errno) dan yang dibayangkan bisa menjadi masalah tergantung pada kapan tepatnya per-benang errnolokasi dialokasikan .
zwol
2

Berikut adalah gambaran umum yang bagus tentang apa yang terjadi selama startup program sebelumnya main . Secara khusus, ini menunjukkan bahwa itu __startadalah titik masuk sebenarnya ke program Anda dari sudut pandang OS.

Ini adalah alamat pertama dari mana penunjuk instruksi akan mulai menghitung dalam program Anda.

Kode di sana memanggil beberapa rutinitas pustaka runtime C hanya untuk melakukan beberapa housekeeping, kemudian memanggil Anda main, dan kemudian menurunkan semuanya dan memanggil exitdengan kode keluar apa pun yang maindikembalikan.


Sebuah gambar memiliki makna ribuan kata:

C runtime startup diagram


PS: jawaban ini ditransplantasikan dari pertanyaan lain yang telah ditutup SO membantu sebagai duplikat dari pertanyaan ini.

ulidtko
sumber
Posting silang untuk mempertahankan analisis yang sangat baik dan gambar yang bagus.
ulidtko
1

Kapan seseorang perlu melakukan hal semacam ini?

Ketika Anda menginginkan kode startup Anda sendiri untuk program Anda.

mainbukan entri pertama untuk program C, _startadalah entri pertama di balik tirai.

Contoh di 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

Apakah ada skenario dunia nyata di mana ini akan berguna?

Jika Anda maksud, terapkan sendiri _start:

Ya, di sebagian besar perangkat lunak tertanam komersial yang pernah saya gunakan, kami perlu menerapkannya sendiri _startterkait dengan memori khusus dan persyaratan kinerja kami.

Jika yang Anda maksud, hapus mainfungsi dan ubah ke yang lain:

Tidak, saya tidak melihat keuntungan melakukan itu.

Trevor
sumber