Bagaimana program dengan variabel global yang disebut main daripada fungsi utama bekerja?


97

Pertimbangkan program berikut:

#include <iostream>
int main = ( std::cout << "C++ is excellent!\n", 195 ); 

Menggunakan g ++ 4.8.1 (mingw64) pada OS Windows 7, program dikompilasi dan berjalan dengan baik, mencetak:

C ++ luar biasa!

ke konsol. maintampaknya menjadi variabel global daripada fungsi; bagaimana program ini dapat dijalankan tanpa fungsi main()? Apakah kode ini sesuai dengan standar C ++? Apakah perilaku program didefinisikan dengan baik? Saya juga telah menggunakan -pedantic-errorsopsi tetapi program masih mengkompilasi dan berjalan.


11
@ πάνταῥεῖ: mengapa tag pengacara bahasa diperlukan?
Destructor

14
Perhatikan bahwa 195opcode untuk RETinstruksi, dan dalam konvensi pemanggilan C, pemanggil membersihkan stack.
Brian

2
@PravasiMeet "lalu bagaimana program ini dijalankan" - apakah menurut Anda kode inisialisasi untuk variabel harus dijalankan (bahkan tanpa main()fungsi? Pada kenyataannya, mereka sama sekali tidak terkait.)
The Paramagnetic Croissant

4
Saya termasuk orang yang menemukan bahwa program segfaults apa adanya (64-bit linux, g ++ 5.1 / clang 3.6). Namun, saya dapat memperbaiki ini dengan mengubahnya menjadi int main = ( std::cout << "C++ is excellent!\n", exit(0),1 );(dan termasuk <cstdlib>), meskipun program tersebut secara hukum tidak berbentuk.
Mike Kinghan

11
@ Brian Anda harus menyebutkan arsitektur saat membuat pernyataan seperti itu. Seluruh dunia bukanlah VAX. Atau x86. Atau terserah.
dmckee --- mantan moderator kucing

Jawaban:


84

Sebelum masuk ke inti pertanyaan tentang apa yang sedang terjadi, penting untuk menunjukkan bahwa program memiliki format yang salah sesuai laporan cacat 1886: Hubungan bahasa untuk main () :

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

Versi terbaru dari clang dan gcc membuat ini menjadi kesalahan dan program tidak dapat dikompilasi ( lihat contoh langsung gcc ):

error: cannot declare '::main' to be a global variable
int main = ( std::cout << "C++ is excellent!\n", 195 ); 
    ^

Jadi mengapa tidak ada diagnosis di versi gcc dan clang? Laporan kerusakan ini bahkan tidak memiliki penyelesaian yang diusulkan hingga akhir 2014 dan oleh karena itu kasus ini baru-baru ini secara eksplisit bentuknya buruk, yang memerlukan diagnosis.

Sebelum ini, sepertinya ini akan menjadi perilaku tidak terdefinisi karena kita melanggar persyaratan wajib dari draf standar C ++ dari bagian 3.6.1 [basic.start.main] :

Suatu program harus berisi fungsi global yang disebut main, yang merupakan permulaan program yang ditentukan. [...]

Perilaku tidak terdefinisi tidak dapat diprediksi dan tidak memerlukan diagnosis. Ketidakkonsistenan yang kita lihat dengan mereproduksi perilaku adalah perilaku khas yang tidak terdefinisi.

Jadi, apa sebenarnya yang dilakukan kode tersebut dan mengapa dalam beberapa kasus kode itu membuahkan hasil? Mari kita lihat apa yang kita punya:

declarator  
|        initializer----------------------------------
|        |                                           |
v        v                                           v
int main = ( std::cout << "C++ is excellent!\n", 195 ); 
    ^      ^                                   ^
    |      |                                   |
    |      |                                   comma operator
    |      primary expression
global variable of type int

Kami memiliki mainyang merupakan int dinyatakan dalam namespace global dan sedang diinisialisasi, variabel memiliki durasi penyimpanan statis. Ini adalah implementasi yang ditentukan apakah inisialisasi akan dilakukan sebelum upaya untuk memanggil maindilakukan tetapi tampaknya gcc melakukan ini sebelum memanggil main.

Kode menggunakan operator koma , operan kiri adalah ekspresi nilai yang dibuang dan digunakan di sini hanya untuk efek samping pemanggilan std::cout. Hasil dari operator koma adalah operan kanan yang dalam hal ini adalah prvalue 195yang diberikan ke variabel main.

Kita dapat melihat sergej menunjukkan perakitan yang dihasilkan menunjukkan yang coutdipanggil selama inisialisasi statis. Meskipun poin yang lebih menarik untuk diskusi melihat sesi godbolt langsung adalah ini:

main:
.zero   4

dan selanjutnya:

movl    $195, main(%rip)

Skenario yang mungkin terjadi adalah bahwa program melompat ke simbol yang mainmengharapkan kode valid ada di sana dan dalam beberapa kasus akan seg-fault . Jadi jika itu kasusnya, kami berharap menyimpan kode mesin yang valid dalam variabel maindapat menghasilkan program yang bisa diterapkan , dengan asumsi kami berada di segmen yang memungkinkan eksekusi kode. Kita bisa melihat entri IOCCC 1984 melakukan hal itu .

Tampaknya kita bisa mendapatkan gcc untuk melakukan ini di C menggunakan ( lihat langsung ):

const int main = 195 ;

Itu seg-kesalahan jika variabel maintidak const mungkin karena tidak terletak di lokasi yang dapat dieksekusi, Hat Tip untuk komentar ini di sini yang memberi saya ide ini.

Juga lihat jawaban FUZxxl di sini untuk versi khusus C dari pertanyaan ini.


Mengapa implementasi tidak memberikan peringatan apa pun juga. (Saat saya menggunakan -Wall & -Wextra masih belum memberikan peringatan tunggal). Mengapa? Apa pendapat Anda tentang jawaban @Mark B untuk pertanyaan ini?
Destructor

IMHO, kompilator tidak boleh memberikan peringatan karena mainbukan pengenal yang dipesan (3.6.1 / 3). Dalam hal ini, menurut saya penanganan VS2013 untuk kasus ini (lihat jawaban Francis Cugler) lebih tepat dalam penanganannya daripada gcc & clang.
cdmh

@PravasiMeet Saya memperbarui jawaban saya tentang mengapa versi gcc sebelumnya tidak memberikan diagnostik.
Shafik Yaghmour

2
... dan memang, ketika saya menguji program OP di Linux / x86-64, dengan g ++ 5.2 (yang menerima program - saya kira Anda tidak bercanda tentang "versi terbaru"), itu macet persis seperti yang saya harapkan akan.
zwol

1
@Walter Saya tidak percaya ini adalah duplikat yang pertama mengajukan pertanyaan yang jauh lebih sempit. Jelas ada sekelompok pengguna SO yang memiliki pandangan yang lebih reduksionis atas duplikat yang menurut saya tidak terlalu masuk akal karena kita dapat meringkas sebagian besar pertanyaan SO ke beberapa versi pertanyaan lama tetapi SO tidak akan terlalu berguna.
Shafik Yaghmour

20

Dari 3.6.1 / 1:

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.

Dari sini sepertinya g ++ kebetulan mengizinkan program (mungkin sebagai klausa "berdiri bebas") tanpa fungsi utama.

Kemudian dari 3.6.1 / 3:

Fungsi utama tidak boleh digunakan (3.2) dalam program. Keterkaitan (3.5) utama adalah implementasi yang ditentukan. Program yang mendeklarasikan main sebagai inline atau statis tidak berbentuk. Nama utama tidak dicadangkan.

Jadi di sini kita belajar bahwa tidak masalah untuk memiliki variabel integer bernama main.

Terakhir, jika Anda bertanya-tanya mengapa output dicetak, inisialisasi int mainmenggunakan operator koma untuk mengeksekusi coutpada init statis dan kemudian memberikan nilai integral aktual untuk melakukan inisialisasi.


7
Menarik untuk dicatat bahwa penautan gagal jika Anda mengganti nama mainmenjadi yang lain: (.text+0x20): undefined reference to main '``
Fred Larson

1
Tidakkah Anda harus menentukan ke gcc bahwa program Anda berdiri bebas?
Shafik Yaghmour

9

gcc 4.8.1 menghasilkan rakitan x86 berikut:

.LC0:
    .string "C++ is excellent!\n"
    subq    $8, %rsp    #,
    movl    std::__ioinit, %edi #,
    call    std::ios_base::Init::Init() #
    movl    $__dso_handle, %edx #,
    movl    std::__ioinit, %esi #,
    movl    std::ios_base::Init::~Init(), %edi  #,
    call    __cxa_atexit    #
    movl    $.LC0, %esi #,
    movl    std::cout, %edi #,
    call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)   #
    movl    $195, main(%rip)    #, main
    addq    $8, %rsp    #,
    ret
main:
    .zero   4

Perhatikan bahwa coutdipanggil selama inisialisasi, bukan dalam mainfungsi!

.zero 4mendeklarasikan 4 (0-diinisialisasi) byte mulai dari lokasi main, di mana mainnama variabel [!] .

The mainsimbol ditafsirkan sebagai awal program. Perilakunya tergantung pada platform.


1
Perhatikan seperti yang ditunjukkan Brian 195 adalah opcode untuk retbeberapa arsitektur. Jadi mengatakan nol instruksi mungkin tidak akurat.
Shafik Yaghmour

@ShafikYaghmour Terima kasih atas komentar Anda, Anda benar. Saya mengacaukan arahan assembler.
sergej

8

Itu adalah program yang cacat. Itu macet di lingkungan pengujian saya, cygwin64 / g ++ 4.9.3.

Dari standar:

3.6.1 Fungsi utama [basic.start.main]

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


Saya pikir sebelum laporan kerusakan yang saya kutip, ini hanyalah perilaku yang tidak terdefinisi.
Shafik Yaghmour

@ShafikYaghmour, Apakah itu prinsip umum yang akan diterapkan di semua tempat di mana penggunaan standar harus ?
R Sahu

Saya ingin mengatakan ya tetapi saya belum melihat penjelasan yang baik tentang perbedaannya. Dari apa yang dapat saya katakan dari diskusi ini , NDR yang salah bentuk dan perilaku tidak terdefinisi mungkin sama karena keduanya tidak memerlukan diagnostik. Ini sepertinya menyiratkan bentuk yang buruk dan UB berbeda tetapi tidak yakin.
Shafik Yaghmour

3
C99 bagian 4 ("Kesesuaian") membuat ini tidak ambigu: "Jika persyaratan 'harus' atau 'tidak boleh' yang muncul di luar batasan dilanggar, perilaku tidak ditentukan." Saya tidak dapat menemukan kata-kata yang setara di C ++ 98 atau C ++ 11, tetapi saya sangat curiga komite bermaksud ada di sana. (Komite C dan C ++ benar-benar perlu duduk dan menyelesaikan semua perbedaan terminologis antara kedua standar.)
zwol

7

Alasan saya percaya ini berfungsi adalah bahwa kompilator tidak tahu itu sedang menyusun main()fungsi sehingga mengkompilasi integer global dengan efek samping tugas.

The format objek yang ini terjemahan unit dikompilasi menjadi tidak mampu membedakan antara simbol fungsi dan simbol variabel .

Jadi linker dengan senang hati menautkan ke simbol utama (variabel) dan memperlakukannya seperti pemanggilan fungsi. Tetapi tidak sampai sistem runtime telah menjalankan kode inisialisasi variabel global.

Ketika saya menjalankan sampel itu dicetak tetapi kemudian itu menyebabkan kesalahan-seg . Saya berasumsi saat itulah sistem runtime mencoba mengeksekusi variabel int seolah-olah itu adalah sebuah fungsi .


4

Saya sudah mencoba ini pada OS Win7 64bit menggunakan VS2013 dan dikompilasi dengan benar tetapi ketika saya mencoba membangun aplikasi saya mendapatkan pesan ini dari jendela keluaran.

1>------ Build started: Project: tempTest, Configuration: Debug Win32 ------
1>LINK : fatal error LNK1561: entry point must be defined
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

2
FWIW, itu kesalahan penaut, bukan pesan dari debugger. Kompilasi berhasil, tetapi linker tidak dapat menemukan fungsi main()karena merupakan variabel jenisint
cdmh

Terima kasih atas balasannya, saya akan mengubah jawaban awal saya untuk mencerminkan ini.
Francis Cugler

-1

Anda melakukan pekerjaan rumit di sini. Sebagai main (entah bagaimana) bisa dideklarasikan menjadi integer. Anda menggunakan operator daftar untuk mencetak pesan & kemudian menetapkan 195 padanya. Seperti yang dikatakan oleh seseorang di bawah ini, bahwa itu tidak nyaman dengan C ++, itu benar. Tetapi karena kompiler tidak menemukan nama yang ditentukan pengguna, main, itu tidak mengeluh. Ingat main bukan fungsi yang ditentukan sistem, fungsi yang ditentukan penggunanya & hal dari mana program mulai dijalankan adalah Modul Utama, bukan main (), secara khusus. Sekali lagi main () dipanggil oleh fungsi startup yang dieksekusi oleh loader secara sengaja. Kemudian semua variabel Anda diinisialisasi, & saat menginisialisasi, output seperti itu. Itu dia. Program tanpa main () oke, tapi tidak standar.

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.