Apakah 'int main;' program C / C ++ yang valid?


113

Saya bertanya karena kompiler saya tampaknya berpikir demikian, meskipun saya tidak.

echo 'int main;' | cc -x c - -Wall
echo 'int main;' | c++ -x c++ - -Wall

Clang tidak mengeluarkan peringatan atau kesalahan dengan ini, dan gcc hanya mengeluarkan peringatan lemah lembut:, 'main' is usually a function [-Wmain]tetapi hanya jika dikompilasi sebagai C. Menentukan a -std=tampaknya tidak penting.

Jika tidak, itu mengkompilasi dan menautkan dengan baik. Tapi saat dieksekusi, itu segera berakhir dengan SIGBUS(untuk saya).

Membaca jawaban (sangat baik) di What should main () return dalam C dan C ++? dan melihat sekilas spesifikasi bahasa, bagi saya jelas terlihat bahwa fungsi utama diperlukan. Tapi verbiage dari gcc's -Wmain('main' biasanya merupakan sebuah fungsi) (dan kelangkaan error di sini) sepertinya menyarankan sebaliknya.

Tapi kenapa? Apakah ada penggunaan kasus tepi atau "historis" yang aneh untuk ini? Ada yang tahu apa yang memberi?

Maksud saya, saya kira, adalah saya benar-benar berpikir ini harus menjadi kesalahan dalam lingkungan yang dihosting, eh?


6
Untuk membuat gcc menjadi kompiler yang (kebanyakan) standar yang Anda butuhkangcc -std=c99 -pedantic ...
pmg

3
@pmg Ini peringatan yang sama, dengan atau tanpa -pedanticatau apapun -std. Sistem saya c99juga mengkompilasi ini tanpa peringatan atau kesalahan ...
Geoff Nixon

3
Sayangnya, jika Anda "cukup pintar", Anda dapat membuat hal-hal yang dapat diterima oleh kompilator tetapi tidak masuk akal. Dalam kasus ini, Anda menautkan pustaka runtime C untuk memanggil variabel yang dipanggil main, yang kemungkinan tidak akan berfungsi. Jika Anda menginisialisasi main dengan nilai yang "benar", itu mungkin benar-benar mengembalikan ...
Mats Petersson

7
Dan bahkan jika itu valid, itu adalah hal yang buruk untuk dilakukan (kode tidak terbaca). BTW, mungkin berbeda dalam implementasi yang dihosting dan dalam implementasi yang berdiri sendiri (yang tidak diketahui main)
Basile Starynkevitch

1
Untuk waktu yang lebih menyenangkan, cobamain=195;
imallett

Jawaban:


97

Karena pertanyaannya diberi tag ganda sebagai C dan C ++, alasan untuk C ++ dan C akan berbeda:

  • C ++ menggunakan name mangling untuk membantu linker membedakan antara simbol yang identik secara tekstual dari berbagai jenis, misalnya variabel global xyzdan fungsi global yang berdiri sendiri xyz(int). Namun, namanya maintidak pernah rusak.
  • C tidak menggunakan mangling, jadi mungkin saja program membingungkan linker dengan memberikan satu jenis simbol di tempat simbol yang berbeda, dan program berhasil membuat link.

Itulah yang terjadi di sini: penaut berharap menemukan simbol main, dan memang demikian. Ini "menyambungkan" simbol itu seolah-olah itu adalah sebuah fungsi, karena tidak ada yang lebih baik. Porsi pustaka runtime yang melewati kontrol untuk mainmeminta linker main, jadi linker memberinya simbol main, membiarkan fase link selesai. Tentu saja ini gagal saat runtime, karenamain bukan fungsi.

Berikut ilustrasi lain dari masalah yang sama:

file xc:

#include <stdio.h>
int foo(); // <<== main() expects this
int main(){
    printf("%p\n", (void*)&foo);
    return 0;
}

file yc:

int foo; // <<== external definition supplies a symbol of a wrong kind

menyusun:

gcc x.c y.c

Ini mengkompilasi, dan itu mungkin akan berjalan, tetapi ini adalah perilaku yang tidak ditentukan, karena jenis simbol yang dijanjikan kepada kompilator berbeda dari simbol sebenarnya yang diberikan ke linker.

Sejauh peringatan berjalan, saya pikir itu masuk akal: C memungkinkan Anda membangun perpustakaan yang tidak memiliki mainfungsi, sehingga kompilator membebaskan nama mainuntuk penggunaan lain jika Anda perlu mendefinisikan variabel mainkarena alasan yang tidak diketahui.


3
Padahal, compiler C ++ memperlakukan fungsi utama secara berbeda. Namanya tidak akan rusak bahkan tanpa "C" eksternal. Saya kira itu karena jika tidak maka perlu memancarkan eksternal "C" mainnya sendiri, untuk memastikan penautan.
UldisK

@UldisK Ya, saya melihat ini sendiri, dan menurut saya cukup menarik. Masuk akal, tapi aku tidak pernah memikirkannya.
Geoff Nixon

2
Sebenarnya hasil untuk C ++ dan C tidak berbeda, seperti yang ditunjukkan di sini - maintidak tunduk pada nama mangling (jadi sepertinya) di C ++, apakah itu fungsi atau tidak.
Geoff Nixon

4
@nm Saya rasa interpretasi Anda atas pertanyaan itu terlalu sempit: selain menanyakan pertanyaan di judul postingan, OP dengan jelas mencari penjelasan mengapa programnya dikompilasi di tempat pertama ("kompiler saya sepertinya berpikir begitu, meskipun saya tidak ") serta saran mengapa hal itu dapat berguna untuk didefinisikan mainsebagai apa pun selain fungsi. Jawabannya menawarkan penjelasan untuk kedua bagian tersebut.
dasblinkenlight

1
Bahwa simbol utama tidak tunduk pada nama mangling tidak relevan. Tidak ada penyebutan nama mangling dalam standar C ++. Penguraian nama adalah masalah implementasi.
David Hammen

30

mainbukan kata reserved itu hanya identifier yang telah ditetapkan (seperti cin, endl, npos...), sehingga Anda bisa mendeklarasikan variabel yang disebutmain , menginisialisasi dan kemudian mencetak nilainya.

Tentu saja:

  • peringatan ini berguna karena ini cukup rawan kesalahan;
  • Anda dapat memiliki file sumber tanpa main()fungsi (perpustakaan).

EDIT

Beberapa referensi:

  • main bukan kata khusus (C ++ 11):

    Fungsi maintersebut tidak boleh digunakan dalam program. Keterkaitan (3.5) maindidefinisikan oleh implementasi. Sebuah program yang mendefinisikan main sebagai dihapus atau yang menyatakan main menjadi inline,static atau constexprsakit-terbentuk. Nama maintersebut tidak dicadangkan. [Contoh: fungsi anggota, kelas dan enumerasi dapat dipanggil main, seperti halnya entitas di ruang nama lain. - contoh akhir]

    C ++ 11 - [basic.start.main] 3.6.1.3

    [2.11 / 3] [...] beberapa pengenal dicadangkan untuk digunakan oleh implementasi C ++ dan pustaka standar (17.6.4.3.2) dan tidak boleh digunakan sebaliknya; tidak diperlukan diagnostik.

    [17.6.4.3.2 / 1] Kumpulan nama dan tanda tangan fungsi tertentu selalu dicadangkan untuk implementasi:

    • Setiap nama yang berisi garis bawah ganda __ atau dimulai dengan garis bawah diikuti dengan huruf besar (2.12) dicadangkan untuk implementasi untuk penggunaan apa pun.
    • Setiap nama yang dimulai dengan garis bawah dicadangkan untuk implementasi untuk digunakan sebagai nama di namespace global.
  • Kata yang dipesan dalam bahasa pemrograman .

    Kata yang dicadangkan mungkin tidak didefinisikan ulang oleh pemrogram, tetapi kata yang ditentukan sebelumnya sering kali dapat diganti dalam beberapa kapasitas. Ini adalah kasus main: ada cakupan di mana deklarasi menggunakan pengenal itu mendefinisikan ulang artinya.


- Kurasa aku agak terpedaya oleh fakta bahwa (seperti yang jadi kesalahan rawan), mengapa ini adalah peringatan (tidak kesalahan), dan mengapa itu hanya peringatan ketika dikompilasi sebagai C - Tentu, Anda dapat mengkompilasi tanpa sebuah main()fungsi, tetapi Anda tidak bisa menghubungkannya sebagai sebuah program. Apa yang terjadi di sini adalah bahwa program "valid" sedang ditautkan tanpa main(), hanya a main.
Geoff Nixon

7
cindan endltidak ada di namespace default - mereka ada di stdnamespace. nposadalah anggota dari std::basic_string.
AnotherParker

1
main adalah dicadangkan sebagai nama global. Tidak ada hal lain yang Anda sebutkan, atau main, yang ditentukan sebelumnya.
Potatoswatter

1
Lihat C ++ 14 §3.6.1 dan C11 §5.1.2.2.1 untuk batasan tentang apa mainyang diperbolehkan. C ++ mengatakan "Implementasi tidak akan menentukan fungsi utama sebelumnya" dan C mengatakan "Implementasi menyatakan tidak ada prototipe untuk fungsi ini."
Potatoswatter

@manlio: harap jelaskan apa yang Anda kutip. Sedangkan untuk C sederhana kutipannya salah. Jadi saya kira ini adalah salah satu dari standar c ++ bukan?
dhein

19

Apakah int main;program C / C ++ valid?

Tidak sepenuhnya jelas apa itu program C / C ++.

Apakah int main;program C valid?

Iya. Penerapan berdiri bebas diizinkan untuk menerima program semacam itu.maintidak harus memiliki arti khusus dalam lingkungan yang berdiri sendiri.

Ini tidak valid di lingkungan yang dihosting.

Apakah int main;program C ++ valid?

Dito.

Mengapa itu crash?

Program tidak harus masuk akal di lingkungan Anda . Dalam lingkungan yang berdiri sendiri, permulaan dan penghentian program, dan arti dari main, ditentukan oleh implementasi.

Mengapa kompilator memperingatkan saya?

Kompilator mungkin memperingatkan Anda tentang apa pun yang diinginkannya, selama ia tidak menolak program yang sesuai. Di sisi lain, hanya peringatan yang diperlukan untuk mendiagnosis program yang tidak sesuai. Karena unit terjemahan ini tidak dapat menjadi bagian dari program host yang valid, pesan diagnostik dibenarkan.

Apakah gcclingkungan berdiri bebas, atau lingkungan yang dihosting?

Iya.

gccmendokumentasikan -ffreestandingbendera kompilasi. Tambahkan, dan peringatan itu hilang. Anda mungkin ingin menggunakannya saat membangun, mis. Kernel atau firmware.

g++tidak mendokumentasikan bendera tersebut. Memasoknya sepertinya tidak berpengaruh pada program ini. Mungkin aman untuk mengasumsikan bahwa lingkungan yang disediakan oleh g ++ dihosting. Tidak adanya diagnostik dalam kasus ini merupakan bug.


17

Ini adalah peringatan karena secara teknis tidak dilarang. Kode startup akan menggunakan lokasi simbol "main" dan melompat ke sana dengan tiga argumen standar (argc, argv dan envp). Tidak, dan pada waktu penautan tidak dapat memeriksa apakah itu benar-benar sebuah fungsi, atau bahkan memiliki argumen tersebut. Ini juga mengapa int main (int argc, char ** argv) berfungsi - kompilator tidak tahu tentang argumen envp dan kebetulan tidak digunakan, dan ini disebut pembersihan pemanggil.

Sebagai lelucon, Anda bisa melakukan sesuatu seperti

int main = 0xCBCBCBCB;

pada mesin x86 dan, mengabaikan peringatan dan hal-hal serupa, itu tidak hanya akan dikompilasi tetapi juga benar-benar berfungsi.

Seseorang menggunakan teknik yang mirip dengan ini untuk menulis (semacam) yang dapat dijalankan yang berjalan pada beberapa arsitektur secara langsung - http://phrack.org/issues/57/17.html#article . Itu juga digunakan untuk memenangkan IOCCC - http://www.ioccc.org/1984/mullender/mullender.c .


1
"Ini adalah peringatan karena secara teknis tidak dilarang" - ini tidak valid di C ++.
Cheers and hth. - Alf

3
"tiga argumen standar (argc, argv dan envp)" - di sini Anda mungkin berbicara tentang standar Posix.
Cheers and hth. - Alf

Di sistem saya (Ubuntu 14 / x64), baris berikut berfungsi dengan gcc:int main __attribute__ ((section (".text")))= 0xC3C3C3C3;
csharpfolk

@ Cheersandhth.-Alf Dua yang pertama standar, yang ketiga adalah POSIX.
keren

9

Apakah ini program yang valid?

Tidak.

Ini bukan program karena tidak memiliki bagian yang dapat dieksekusi.

Apakah valid untuk dikompilasi?

Iya.

Bisakah itu digunakan dengan program yang valid?

Iya.

Tidak semua kode yang dikompilasi harus dapat dieksekusi agar valid. Contohnya adalah perpustakaan statis dan dinamis.

Anda telah membuat file objek secara efektif. Ini bukan eksekusi yang valid, namun program lain dapat menautkan ke objek maindalam file yang dihasilkan dengan memuatnya saat runtime.

Haruskah ini kesalahan?

Secara tradisional, C ++ memungkinkan pengguna untuk melakukan hal-hal yang tampaknya tidak valid digunakan tetapi sesuai dengan sintaks bahasa.

Maksud saya yakin, ini dapat diklasifikasikan ulang sebagai kesalahan, tetapi mengapa? Apa gunanya peringatan itu tidak?

Selama ada kemungkinan teoretis dari fungsionalitas ini digunakan dalam kode aktual, sangat tidak mungkin bahwa memiliki objek non-fungsi yang dipanggil mainakan menghasilkan kesalahan menurut bahasanya.


Ini menciptakan simbol yang terlihat secara eksternal bernama main. Bagaimana program yang valid, yang harus memiliki fungsi yang terlihat secara eksternal bernama main, menautkannya?
Keith Thompson

@Keompson Memuat saat runtime. Akan menjelaskan.
Michael Gazonda

Itu bisa karena tidak bisa membedakan antara tipe simbol. Menghubungkan berfungsi dengan baik - eksekusi (kecuali dalam kasus yang dibuat dengan hati-hati) tidak.
Chris Stratton

1
@ChrisStratton: Saya pikir argumen Keith adalah bahwa penautan gagal karena simbol dikalikan ... karena "program yang valid" tidak akan menjadi program yang valid kecuali ia mendefinisikan suatu mainfungsi.
Ben Voigt

@BenVoigt Tetapi jika muncul di perpustakaan, maka penautan tidak akan (dan mungkin tidak dapat) gagal, karena pada waktu tautan program, int main;definisi tidak akan terlihat.

6

Saya ingin menambahkan jawaban yang sudah diberikan dengan mengutip standar bahasa yang sebenarnya.

Apakah 'int main;' program C yang valid?

Jawaban singkat (pendapat saya): hanya jika implementasi Anda menggunakan "lingkungan eksekusi bebas".

Semua kutipan berikut dari C11

5. Lingkungan

Sebuah implementasi menerjemahkan file sumber C dan menjalankannya program C dalam dua lingkungan sistem pemrosesan data, yang akan disebut lingkungan terjemahan dan lingkungan eksekusi [...]

5.1.2 Lingkungan eksekusi

Dua lingkungan eksekusi ditentukan: berdiri bebas dan dihosting. Dalam kedua kasus, program startup terjadi ketika fungsi C yang ditunjuk dipanggil oleh lingkungan eksekusi.

5.1.2.1 Lingkungan berdiri bebas

Dalam lingkungan berdiri bebas (di mana eksekusi program C dapat berlangsung tanpa manfaat apa pun dari sistem operasi), nama dan jenis fungsi yang dipanggil saat program dimulai ditentukan oleh implementasi.

5.1.2.2 Lingkungan yang dihosting

Lingkungan yang dihosting tidak perlu disediakan, tetapi harus sesuai dengan spesifikasi berikut jika ada.

5.1.2.2.1 Memulai program

Fungsi yang dipanggil saat program startup dinamai main . [...] Ini harus didefinisikan dengan tipe kembalian dari int dan tanpa parameter [...] atau dengan dua parameter [...] atau ekuivalen atau dengan cara lain yang ditentukan implementasi.

Dari sini, diamati hal-hal berikut:

  • Program C11 dapat memiliki lingkungan eksekusi yang berdiri sendiri atau dihosting dan valid.
  • Jika memiliki fungsi yang berdiri sendiri, tidak perlu ada fungsi utama.
  • Jika tidak, harus ada satu dengan return vale bertipe int .

Dalam lingkungan eksekusi berdiri bebas, saya berpendapat bahwa ini adalah program yang valid yang tidak memungkinkan startup terjadi, karena tidak ada fungsi yang hadir untuk itu seperti yang dipersyaratkan dalam 5.1.2. Dalam lingkungan eksekusi yang dihosting, saat kode Anda memasukkan objek bernama main , itu tidak dapat memberikan nilai kembali, jadi saya berpendapat bahwa itu bukan program yang valid dalam pengertian ini, meskipun seseorang juga dapat berdebat seperti sebelumnya jika program tersebut tidak dimaksudkan untuk dieksekusi (pada mungkin ingin memberikan data hanya misalnya), maka itu tidak memungkinkan untuk melakukan hal itu.

Apakah 'int main;' program C ++ yang valid?

Jawaban singkat (pendapat saya): hanya jika implementasi Anda menggunakan "lingkungan eksekusi bebas".

Kutipan dari C ++ 14

3.6.1 Fungsi utama

Suatu program harus berisi fungsi global yang disebut main, yang merupakan permulaan program yang ditentukan. Ini adalah implementasi yang ditentukan apakah suatu program dalam lingkungan berdiri bebas diperlukan untuk menentukan fungsi utama. [...] Ini akan memiliki tipe kembalian dari tipe int, tetapi sebaliknya tipenya adalah implementasi-didefinisikan. [...] Nama utama tidak dicadangkan.

Di sini, sebagai lawan dari standar C11, lebih sedikit batasan yang diterapkan pada lingkungan eksekusi berdiri bebas, karena tidak ada fungsi startup yang disebutkan sama sekali, sedangkan untuk lingkungan eksekusi yang dihosting, kasusnya hampir sama dengan C11.

Sekali lagi, saya berpendapat bahwa untuk kasus yang dihosting, kode Anda bukan program C ++ 14 yang valid, tetapi saya yakin itu untuk kasus yang berdiri sendiri.

Karena jawaban saya hanya mempertimbangkan lingkungan eksekusi , saya pikir jawaban dasblinkenlicht ikut bermain, karena nama mangling yang terjadi di lingkungan penerjemahan terjadi sebelumnya. Di sini, saya tidak begitu yakin bahwa kutipan di atas diamati dengan sangat ketat.


4

Maksud saya, saya kira, adalah saya benar-benar berpikir ini harus menjadi kesalahan dalam lingkungan yang dihosting, eh?

Kesalahan itu milik Anda. Anda tidak menentukan fungsi bernama mainyang mengembalikan intdan mencoba menggunakan program Anda di lingkungan yang dihosting.

Misalkan Anda memiliki unit kompilasi yang mendefinisikan variabel global bernama main . Ini mungkin legal dalam lingkungan berdiri bebas karena apa yang merupakan program diserahkan kepada penerapan dalam lingkungan berdiri bebas.

Misalkan Anda memiliki unit kompilasi lain yang mendefinisikan fungsi global bernama mainyang mengembalikan intdan tidak membutuhkan argumen. Inilah yang dibutuhkan program dalam lingkungan yang dihosting.

Semuanya baik-baik saja jika Anda hanya menggunakan unit kompilasi pertama di lingkungan yang berdiri sendiri dan hanya menggunakan yang kedua di lingkungan yang dihosting. Bagaimana jika Anda menggunakan keduanya dalam satu program? Di C ++, Anda telah melanggar aturan satu definisi. Itu adalah perilaku yang tidak terdefinisi. Di C, Anda telah melanggar aturan yang menyatakan bahwa semua referensi ke satu simbol harus konsisten; jika tidak, itu adalah perilaku yang tidak terdefinisi. Perilaku tidak terdefinisi adalah "keluar dari penjara, gratis!" kartu kepada pengembang implementasi. Apa pun yang dilakukan implementasi sebagai respons terhadap perilaku yang tidak ditentukan akan sesuai dengan standar. Implementasinya tidak harus memperingatkan, apalagi mendeteksi, perilaku tidak terdefinisi.

Bagaimana jika Anda menggunakan hanya satu dari unit kompilasi itu, tetapi Anda menggunakan yang salah (yang mana yang Anda lakukan)? Di C, situasinya jelas. Kegagalan untuk menentukan fungsi maindi salah satu dari dua bentuk standar di lingkungan yang dihosting adalah perilaku yang tidak ditentukan. Misalkan Anda tidak mendefinisikanmain sama sekali. Compiler / linker tidak perlu mengatakan apapun tentang kesalahan ini. Bahwa mereka mengeluh adalah kebaikan atas nama mereka. Bahwa program C dikompilasi dan ditautkan tanpa kesalahan adalah kesalahan Anda, bukan kompiler.

Ini agak kurang jelas di C ++ karena kegagalan untuk mendefinisikan fungsi maindi lingkungan yang dihosting adalah kesalahan daripada perilaku yang tidak ditentukan (dengan kata lain, ini harus didiagnosis). Namun, aturan satu definisi dalam C ++ berarti penaut bisa jadi agak bodoh. Tugas penaut adalah menyelesaikan referensi eksternal, dan berkat aturan satu definisi, penaut tidak harus mengetahui arti simbol tersebut. Anda menyediakan simbol bernama main, linker mengharapkan untuk melihat simbol bernama main, jadi semuanya baik-baik saja sejauh yang bersangkutan linker.


4

Untuk C sejauh ini adalah implementasi perilaku yang ditentukan.

Seperti yang dikatakan ISO / IEC9899:

5.1.2.2.1 Memulai program

1 Fungsi yang dipanggil saat program startup dinamai main. Implementasinya menyatakan tidak ada prototipe untuk fungsi ini. Ini harus didefinisikan dengan tipe kembalian int dan tanpa parameter:

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

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

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

atau setara; atau dengan cara lain yang ditentukan implementasi.


3

Tidak, ini bukan program yang valid.

Untuk C ++ ini baru-baru ini secara eksplisit dibuat tidak benar oleh laporan cacat 1886: Hubungan bahasa untuk main () yang mengatakan:

Tampaknya tidak ada batasan apa pun dalam memberikan tautan bahasa eksplisit main (), tetapi itu mungkin salah bentuk atau didukung secara bersyarat.

dan bagian dari resolusi termasuk perubahan berikut:

Program yang mendeklarasikan main variabel pada lingkup global atau yang menyatakan nama main dengan keterkaitan bahasa C (dalam namespace apa pun) tidak berbentuk.

Kita dapat menemukan kata-kata ini dalam standar draf C ++ N4527 terbaru yang merupakan draf C ++ 1z.

Versi terbaru dari clang dan gcc sekarang menjadikan ini sebagai kesalahan ( lihat langsung ):

error: main cannot be declared as global variable
int main;
^

Sebelum laporan kerusakan ini, itu adalah perilaku tidak terdefinisi yang tidak memerlukan diagnostik. Di sisi lain, kode yang bentuknya buruk memerlukan diagnostik, kompilator dapat menjadikannya sebagai peringatan atau kesalahan.


Terima kasih atas pembaruannya! Senang melihat ini sekarang diambil dengan diagnostik kompiler. Namun, saya harus mengatakan saya menemukan perubahan dalam standar C ++ membingungkan. (Untuk latar belakang, lihat komentar di atas mengenai nama mangling dari main().) Saya memahami alasan untuk melarang main()memiliki spesifikasi tautan eksplisit, tetapi saya tidak mengerti itu mengamanatkan yang main()memiliki tautan C ++ . Tentu saja standar tidak langsung alamat bagaimana menangani ABI linkage / nama mangling, tetapi dalam prakteknya (katakanlah, dengan Itanium ABI) ini akan memotong-motong main()untuk _Z4mainv. Apa yang saya lewatkan?
Geoff Nixon

Saya pikir komentar supercat mencakup itu. Jika implementasi melakukan hal sendiri sebelum memanggil main yang ditentukan pengguna maka ia dapat dengan mudah memilih untuk memanggil nama yang rusak sebagai gantinya.
Shafik Yaghmour
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.