Bagaimana cara kerja metode main () di C?

96

Saya tahu ada dua tanda tangan berbeda untuk menulis metode utama -

int main()
{
   //Code
}

atau untuk menangani argumen baris perintah, kami menuliskannya sebagai-

int main(int argc, char * argv[])
{
   //code
}

Dalam C++Aku tahu kita bisa membebani metode, tetapi dalam Cbagaimana compiler menangani dua tanda tangan yang berbeda dari mainfungsi?

Ritesh
sumber
14
Overloading mengacu pada memiliki dua metode dengan nama yang sama dalam program yang sama. Anda hanya dapat memiliki satu mainmetode dalam satu program di C(atau, sebenarnya, dalam hampir semua bahasa dengan konstruksi seperti itu).
Kyle Strand
12
C tidak memiliki metode; itu memiliki fungsi. Metode adalah implementasi ujung belakang dari fungsi "generik" berorientasi objek. Program memanggil fungsi dengan beberapa argumen objek, dan sistem objek memilih sebuah metode (atau mungkin sekumpulan metode) berdasarkan tipenya. C tidak memiliki semua hal ini kecuali Anda mensimulasikannya sendiri.
Kaz
4
Untuk diskusi mendalam tentang titik masuk program - tidak secara khusus main- saya merekomendasikan buku klasik John R. Levines "Linkers & Loaders".
Andreas Spindler
1
Di C, bentuk pertama adalah int main(void), bukan int main()(meskipun saya belum pernah melihat kompiler yang menolak int main()formulir).
Keith Thompson
1
@ Harper: ()Formulirnya sudah usang, dan bahkan tidak jelas apakah diizinkan main(kecuali jika penerapannya secara khusus mendokumentasikannya sebagai formulir yang diizinkan). Standar C (lihat 5.1.2.2.1 Memulai program) tidak menyebutkan ()formulir, yang tidak cukup setara dengan ()formulir. Detailnya terlalu panjang untuk komentar ini.
Keith Thompson

Jawaban:

132

Beberapa fitur bahasa C dimulai sebagai peretasan yang kebetulan berhasil.

Beberapa tanda tangan untuk main, serta daftar argumen panjang-variabel, adalah salah satu fiturnya.

Pemrogram memperhatikan bahwa mereka dapat meneruskan argumen tambahan ke suatu fungsi, dan tidak ada hal buruk yang terjadi dengan kompiler yang diberikan.

Ini adalah kasus jika konvensi pemanggilan seperti itu:

  1. Fungsi pemanggilan membersihkan argumen.
  2. Argumen paling kiri lebih dekat ke bagian atas tumpukan, atau ke dasar bingkai tumpukan, sehingga argumen palsu tidak membatalkan pengalamatan.

Satu set konvensi pemanggil yang mematuhi aturan ini adalah pengalihan parameter berbasis tumpukan di mana pemanggil memunculkan argumen, dan mereka didorong dari kanan ke kiri:

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

Dalam kompiler di mana jenis konvensi pemanggilan ini terjadi, tidak ada kebutuhan khusus yang perlu dilakukan untuk mendukung dua jenis main, atau bahkan jenis tambahan. mainbisa menjadi fungsi tanpa argumen, dalam hal ini ia mengabaikan item yang didorong ke tumpukan. Jika ini adalah fungsi dari dua argumen, maka ia menemukan argcdan argvsebagai dua item tumpukan paling atas. Jika itu adalah varian tiga argumen khusus platform dengan penunjuk lingkungan (ekstensi umum), itu juga akan berfungsi: ia akan menemukan argumen ketiga itu sebagai elemen ketiga dari atas tumpukan.

Jadi panggilan tetap berfungsi untuk semua kasus, memungkinkan satu modul start-up tetap untuk ditautkan ke program. Modul itu bisa ditulis dalam C, dengan fungsi yang menyerupai ini:

/* I'm adding envp to show that even a popular platform-specific variant
   can be handled. */
extern int main(int argc, char **argv, char **envp);

void __start(void)
{
  /* This is the real startup function for the executable.
     It performs a bunch of library initialization. */

  /* ... */

  /* And then: */
  exit(main(argc_from_somewhere, argv_from_somewhere, envp_from_somewhere));
}

Dengan kata lain, modul start ini hanya memanggil main tiga argumen, selalu. Jika main tidak membutuhkan argumen, atau hanya int, char **, itu berfungsi dengan baik, serta jika tidak membutuhkan argumen, karena konvensi pemanggilan.

Jika Anda melakukan hal semacam ini dalam program Anda, itu akan menjadi perilaku nonportable dan dianggap tidak terdefinisi oleh ISO C: mendeklarasikan dan memanggil fungsi dengan satu cara, dan mendefinisikannya dengan cara lain. Tetapi trik startup kompiler tidak harus portabel; itu tidak dipandu oleh aturan untuk program portabel.

Tetapi anggaplah bahwa konvensi pemanggilan sedemikian rupa sehingga tidak dapat bekerja dengan cara ini. Dalam hal ini, kompilator harus memperlakukannya mainsecara khusus. Ketika ia mengetahui bahwa ia sedang mengompilasi mainfungsinya, ia dapat menghasilkan kode yang kompatibel dengan, katakanlah, panggilan tiga argumen.

Artinya, Anda menulis ini:

int main(void)
{
   /* ... */
}

Tetapi ketika kompilator melihatnya, pada dasarnya ia melakukan transformasi kode sehingga fungsi yang dikompilasinya terlihat lebih seperti ini:

int main(int __argc_ignore, char **__argv_ignore, char **__envp_ignore)
{
   /* ... */
}

kecuali bahwa nama __argc_ignoreitu tidak ada secara harfiah. Tidak ada nama seperti itu yang dimasukkan ke dalam cakupan Anda, dan tidak akan ada peringatan tentang argumen yang tidak digunakan. Transformasi kode menyebabkan kompilator memancarkan kode dengan hubungan yang benar yang mengetahui bahwa ia harus membersihkan tiga argumen.

Strategi implementasi lainnya adalah untuk compiler atau mungkin linker untuk membuat custom __startfungsi (atau apapun namanya), atau setidaknya pilih satu dari beberapa alternatif yang telah dikompilasi sebelumnya. Informasi dapat disimpan dalam file objek tentang bentuk yang didukung mainyang digunakan. Linker dapat melihat info ini, dan memilih versi yang benar dari modul start-up yang berisi panggilan mainyang kompatibel dengan definisi program. Implementasi C biasanya hanya memiliki sejumlah kecil bentuk yang didukung mainsehingga pendekatan ini layak.

Kompiler untuk bahasa C99 selalu harus memperlakukan mainsecara khusus, sampai batas tertentu, untuk mendukung peretasan yang jika fungsi berhenti tanpa returnpernyataan, perilakunya seolah-olah return 0dijalankan. Ini, sekali lagi, dapat ditangani dengan transformasi kode. Kompilator memperhatikan bahwa fungsi yang dipanggil mainsedang dikompilasi. Kemudian ia memeriksa apakah ujung tubuh berpotensi dijangkau. Jika demikian, itu menyisipkanreturn 0;

Kaz
sumber
34

Tidak ada overloading mainbahkan di C ++. Fungsi utama adalah titik masuk untuk sebuah program dan seharusnya hanya ada satu definisi.

Untuk Standar C

Untuk lingkungan yang dihosting (itu yang normal), standar C99 mengatakan:

5.1.2.2.1 Memulai program

Fungsi yang dipanggil saat program startup dinamai main. Implementasinya menyatakan tidak ada prototipe untuk fungsi ini. Ini harus ditentukan dengan tipe pengembalian intdan tanpa parameter:

int main(void) { /* ... */ }

atau dengan dua parameter (disebut di sini sebagai argcdan argv, meskipun nama apa pun dapat digunakan, karena bersifat lokal ke fungsi di mana mereka dideklarasikan):

int main(int argc, char *argv[]) { /* ... */ }

atau setara; 9) atau dengan cara lain yang ditentukan implementasi.

9) Jadi, intdapat diganti dengan nama typedef didefinisikan sebagai int, atau jenis argvdapat ditulis sebagai char **argv, dan seterusnya.

Untuk C ++ standar:

3.6.1 Fungsi utama [basic.start.main]

1 Program harus berisi fungsi global yang disebut main, yang merupakan awal program yang ditentukan. [...]

2 Implementasi tidak harus mendefinisikan fungsi utama. Fungsi ini tidak boleh kelebihan beban . Ini harus memiliki tipe kembalian dari tipe int, tetapi sebaliknya tipe implementasi didefinisikan. Semua implementasi harus mengizinkan kedua definisi utama berikut:

int main() { /* ... */ }

dan

int main(int argc, char* argv[]) { /* ... */ }

Standar C ++ secara eksplisit mengatakan "Ini [fungsi utama] harus memiliki tipe kembalian dari tipe int, tetapi jika tidak, tipenya adalah implementasi yang ditentukan", dan membutuhkan dua tanda tangan yang sama seperti standar C.

Dalam lingkungan yang dihosting ( lingkungan AC yang juga mendukung pustaka C) - Sistem Operasi memanggil main.

Dalam lingkungan non-host (Satu ditujukan untuk aplikasi yang disematkan) Anda selalu dapat mengubah titik masuk (atau keluar) dari program Anda menggunakan arahan pra-prosesor seperti

#pragma startup [priority]
#pragma exit [priority]

Dimana prioritas adalah bilangan integral opsional.

Pragma startup menjalankan fungsi sebelum main (berdasarkan prioritas) dan keluar pragma menjalankan fungsi setelah fungsi utama. Jika ada lebih dari satu arahan startup, maka prioritas memutuskan mana yang akan dieksekusi terlebih dahulu.

Sadique
sumber
4
Saya tidak berpikir, jawaban ini sebenarnya menjawab pertanyaan bagaimana compiler sebenarnya menangani situasi tersebut. Jawaban yang diberikan oleh @Kaz menambah wawasan, menurut saya.
Tilman Vogel
4
Saya pikir jawaban ini menjawab pertanyaan dengan lebih baik daripada yang oleh @Kaz. Pertanyaan asli berada di bawah kesan bahwa operator overloading sedang terjadi, dan jawaban ini menyelesaikannya dengan menunjukkan bahwa alih-alih solusi overloading, compiler menerima dua tanda tangan yang berbeda. Detail compiler menarik tetapi tidak diperlukan untuk menjawab pertanyaan tersebut.
Waleed Khan
1
Untuk lingkungan berdiri bebas ("tidak dihosting"), ada lebih banyak hal yang terjadi selain #pragma. Ada interupsi reset dari perangkat keras dan di situlah program dimulai. Dari sana, semua pengaturan dasar dijalankan: susunan pengaturan, register, MMU, pemetaan memori, dll. Kemudian salinan nilai init dari NVM ke variabel penyimpanan statis terjadi (segmen .data), serta "zero-out" pada semua variabel penyimpanan statis yang harus disetel ke nol (segmen .bss). Di C ++, konstruktor objek dengan durasi penyimpanan statis dipanggil. Dan setelah semua itu selesai, maka main dipanggil.
Lundin
8

Tidak perlu kelebihan beban. Ya, ada 2 versi, tetapi hanya satu yang dapat digunakan pada saat itu.

pengguna694733
sumber
5

Ini adalah salah satu asimetri yang aneh dan aturan khusus bahasa C dan C ++.

Menurut pendapat saya, ini hanya ada karena alasan sejarah dan tidak ada logika serius di baliknya. Perhatikan bahwa mainitu juga khusus untuk alasan lain (misalnya maindi C ++ tidak dapat rekursif dan Anda tidak dapat mengambil alamatnya dan di C99 / C ++ Anda diizinkan untuk menghilangkan returnpernyataan akhir ).

Perhatikan juga bahwa bahkan di C ++ ini bukan overload ... baik program memiliki bentuk pertama atau bentuk kedua; tidak bisa keduanya.

6502
sumber
Anda juga dapat menghilangkan returnpernyataan di C (sejak C99).
dreamlax
Di C, Anda dapat menelepon main()dan mencatat alamatnya; C ++ menerapkan batasan yang tidak dimiliki C.
Jonathan Leffler
@JonathanLeffler: Anda benar, tetap. Satu-satunya hal lucu tentang main yang saya temukan di spesifikasi C99 selain kemungkinan untuk menghilangkan nilai yang dikembalikan adalah bahwa karena standarnya diberi kata IIUC Anda tidak dapat meneruskan nilai negatif ke argcsaat berulang (5.1.2.2.1 tidak menentukan batasan pada argcdan argvhanya berlaku untuk panggilan awal ke main).
6502
4

Apa yang tidak biasa mainadalah bahwa itu dapat didefinisikan dengan lebih dari satu cara, itu hanya dapat didefinisikan dengan salah satu dari dua cara yang berbeda.

mainadalah fungsi yang ditentukan pengguna; implementasinya tidak mendeklarasikan prototipe untuk itu.

Hal yang sama berlaku untuk fooatau bar, tetapi Anda dapat mendefinisikan fungsi dengan nama-nama itu sesuka Anda.

Perbedaannya adalah yang maindipanggil oleh implementasi (lingkungan runtime), bukan hanya oleh kode Anda sendiri. Implementasinya tidak terbatas pada semantik pemanggilan fungsi C biasa, sehingga dapat (dan harus) menangani beberapa variasi - tetapi tidak diharuskan untuk menangani banyak kemungkinan yang tak terbatas. The int main(int argc, char *argv[])Bentuk memungkinkan untuk argumen baris perintah, dan int main(void)di C atau int main()C ++ hanya kenyamanan bagi program sederhana yang tidak perlu proses argumen baris perintah.

Adapun bagaimana kompilator menangani ini, itu tergantung pada implementasinya. Sebagian besar sistem mungkin memiliki konvensi pemanggil yang membuat dua bentuk kompatibel secara efektif, dan argumen apa pun yang diteruskan ke yang mainditentukan tanpa parameter akan diabaikan secara diam-diam. Jika tidak, tidak akan sulit bagi compiler atau linker untuk menangani mainsecara khusus. Jika Anda penasaran bagaimana cara kerjanya di sistem Anda, Anda mungkin melihat beberapa daftar perakitan.

Dan seperti banyak hal di C dan C ++, detailnya sebagian besar merupakan hasil dari sejarah dan keputusan sewenang-wenang yang dibuat oleh perancang bahasa dan pendahulunya.

Perhatikan bahwa C dan C ++ sama-sama mengizinkan definisi yang ditentukan implementasi lainnya untuk main- tetapi jarang ada alasan bagus untuk menggunakannya. Dan untuk implementasi yang berdiri sendiri (seperti sistem tertanam tanpa OS), titik masuk program ditentukan oleh implementasi, dan bahkan tidak perlu dipanggil main.

Keith Thompson
sumber
3

Ini mainhanyalah nama untuk alamat awal yang ditentukan oleh linker di mana mainnama default-nya. Semua nama fungsi dalam program memulai alamat tempat fungsi dimulai.

Argumen fungsi didorong / muncul di / dari tumpukan jadi jika tidak ada argumen yang ditentukan untuk fungsi, tidak ada argumen yang didorong / muncul di / di luar tumpukan. Begitulah cara main dapat bekerja dengan atau tanpa argumen.

AndersK
sumber
2

Nah, dua tanda tangan berbeda dari fungsi main () yang sama muncul dalam gambar hanya ketika Anda menginginkannya, maksud saya jika program Anda membutuhkan data sebelum pemrosesan kode Anda yang sebenarnya, Anda dapat meneruskannya melalui penggunaan -

    int main(int argc, char * argv[])
    {
       //code
    }

di mana variabel argc menyimpan jumlah data yang diteruskan dan argv adalah larik pointer ke char yang menunjuk ke nilai yang diteruskan dari konsol. Kalau tidak, itu selalu bagus untuk digunakan

    int main()
    {
       //Code
    }

Namun bagaimanapun juga bisa ada satu dan hanya satu main () dalam sebuah program, karena itu adalah satu-satunya titik di mana dari sebuah program memulai eksekusinya dan karenanya tidak bisa lebih dari satu. (semoga itu layak)

manish
sumber
2

Pertanyaan serupa telah ditanyakan sebelumnya: Mengapa sebuah fungsi tanpa parameter (dibandingkan dengan definisi fungsi sebenarnya) dapat dikompilasi?

Salah satu jawaban peringkat teratas adalah:

Dalam C func()berarti bahwa Anda dapat melewati setiap jumlah argumen. Jika Anda tidak ingin ada argumen maka Anda harus menyatakan sebagaifunc(void)

Jadi, saya kira begitulah cara maindideklarasikan (jika Anda dapat menerapkan istilah "dideklarasikan" ke main). Sebenarnya Anda bisa menulis sesuatu seperti ini:

int main(int only_one_argument) {
    // code
}

dan itu akan tetap dikompilasi dan dijalankan.

varepsilon.dll
sumber
1
Pengamatan luar biasa! Tampaknya penaut cukup memaafkan main, karena ada masalah yang belum disebutkan: bahkan lebih banyak argumen untuk main! "Unix (tapi bukan Posix.1) dan Microsoft Windows" menambahkan char **envp(saya ingat DOS juga mengizinkannya, bukan?), Dan Mac OS X dan Darwin menambahkan lagi char * pointer "informasi yang disediakan OS sewenang-wenang". wikipedia
usr2564301
0

Anda tidak perlu menimpa ini. Karena hanya satu yang akan digunakan dalam satu waktu. Ya ada 2 versi berbeda dari fungsi utama

gautam
sumber