Jak może działać program ze zmienną globalną o nazwie main zamiast funkcji main?

Rozważ następujący program:

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

Używając g++ 4.8.1 (mingw64) w systemie operacyjnym Windows 7, program kompiluje i działa poprawnie, drukuje:

C++ jest znakomity!

Do konsoli. main wydaje się być zmienną globalną, a nie Funkcją; jak ten program może działać bez funkcji main()? Czy ten kod jest zgodny ze standardem C++? Czy zachowanie programu jest dobrze zdefiniowane? Korzystałem również z -pedantic-errors opcja ale program nadal kompiluje i działa.
Author: Shafik Yaghmour, 2015-09-29

7 answers

Zanim przejdziemy do sedna pytania o to, co się dzieje, ważne jest, aby zaznaczyć, że program jest źle uformowany zgodnie z defect report 1886: Language linkage for main():

[...] Program, który deklaruje zmienną main w globalnym zasięgu lub deklaruje nazwę main z linkiem języka C (w dowolnej przestrzeni nazw), jest źle uformowany. [...]

Najnowsze wersje clang i gcc powodują błąd i program nie będzie się kompilował (zobacz przykład GCC na żywo):

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

Więc dlaczego nie było diagnostyki w starszych wersjach gcc i clang? Ten raport wada nawet nie miał proponowanego rozwiązania aż do końca 2014 roku, a więc sprawa ta była tylko bardzo niedawno wyraźnie źle sformowana, co wymaga diagnostyki.

Wcześniej wydaje się, że byłoby to niezdefiniowane zachowanie, ponieważ naruszamy wymóg projektu standardu C++ z sekcji 3.6.1 [podstawowe.zaczynaj.główna]:

Program zawiera globalną funkcję o nazwie main, która jest wyznaczonym startem programu. [...]

Nieokreślone zachowanie jest nieprzewidywalne i nie wymaga diagnostyki. Niespójność, jaką widzimy przy odtwarzaniu zachowania, jest typowym niezdefiniowanym zachowaniem.

Więc co tak naprawdę robi kod i dlaczego w niektórych przypadkach daje rezultaty? Zobaczmy co mamy:

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

Mamy main które jest int zadeklarowaną w globalnej przestrzeni nazw i jest inicjalizowana, zmienna ma statyczny czas przechowywania. Jest to implementacja definiowana, czy inicjalizacja będzie miała miejsce przed próbą wywołania main, ale wygląda na to, że gcc robi to przed wywołaniem main.

Kod używa operatora przecinka , lewy operand jest odrzuconym wyrażeniem wartości i jest tutaj używany wyłącznie do efektu ubocznego wywołania std::cout. Wynikiem operatora przecinka jest prawym operandem, który w tym przypadku jest prvalue 195, który jest przypisany do zmiennej main.

Widzimy sergej wskazuje wygenerowany zespół pokazuje, że {[12] } jest wywoływany podczas inicjalizacji statycznej. Chociaż ciekawszym punktem do dyskusji zobacz sesję live godbolt byłoby to:

main:
.zero   4

I następne:

movl    $195, main(%rip)

Prawdopodobnym scenariuszem jest to, że program przeskakuje do symbolu main oczekując poprawnego kodu i w niektórych przypadkach seg-fault. W takim przypadku spodziewamy się, że przechowywanie poprawnego kodu maszynowego w zmiennej main może prowadzić do wykonalnego programu, zakładając, że znajdujemy się w segmencie umożliwiającym wykonywanie kodu. Widzimy ten wpis IOCCC z 1984 roku robi właśnie to.

Wygląda na to, że możemy zmusić gcc do zrobienia tego w C używając (zobacz to na żywo):

const int main = 195 ;

To seg-błąd jeśli zmienna main nie jest const ponieważ nie znajduje się w miejscu wykonywalnym, Hat Wskazówka do tego komentarz tutaj który dał mi ten pomysł.

Zobacz też FUZxxl odpowiedz tutaj {[18] } na konkretną wersję C tego pytania.

 85
Author: Shafik Yaghmour,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-05 16:03:48

Z 3.6.1/1:

Program zawiera funkcję globalną o nazwie main, która jest wyznaczony początek programu. Jest to implementacja zdefiniowana, czy program w środowisku wolnostojącym jest wymagany do zdefiniowania głównego funkcja.

Z tego wynika, że g++ dopuszcza program (prawdopodobnie jako klauzulę "wolnostojącą") bez głównej funkcji.

Następnie z 3.6.1/3:

Nie stosuje się funkcji main (3.2) w ramach programu. Na linkage (3.5) main jest implementacja zdefiniowana. Program, który deklaruje, że main jest inline lub static jest źle ukształtowany. Nazwa główna to Nie zastrzeżone inaczej.

Więc tutaj dowiadujemy się, że w porządku jest mieć zmienną całkowitą o nazwie main.

Wreszcie, jeśli zastanawiasz się, dlaczego wyjście jest drukowane, inicjalizacja int main używa operatora przecinka do wykonania cout w statycznym init, a następnie podać rzeczywistą wartość całkową, aby wykonać inicjalizacja.

 20
Author: Mark B,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-29 18:48:25

Gcc 4.8.1 generuje następujący zestaw x86:

.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

Zauważ, że {[1] } jest wywoływana podczas inicjalizacji, a nie w funkcji main!

.zero 4 deklaruje 4 (0-inicjalizowane) bajty rozpoczynające się w miejscu main, gdzie main jest nazwą zmiennej [!].

Symbol main jest interpretowany jako początek programu. Zachowanie zależy od platformy.

 9
Author: sergej,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-07 22:47:40

To jest źle uformowany program. Wywala się na moim środowisku testowym, cygwin64 / g++ 4.9.3.

Ze standardu:

3.6.1 Main function [basic.zaczynaj.main]

1 program zawiera globalną funkcję o nazwie main, która jest wyznaczonym startem programu.

 8
Author: R Sahu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55

Powodem, dla którego uważam, że to działa, jest to, że kompilator nie wie, że kompiluje funkcję main(), więc kompiluje globalną liczbę całkowitą z efektami ubocznymi przypisania.

Format obiektu , do którego kompilowana jest jednostka translacji , nie jest w stanie odróżnić symbolu funkcji od symbolu zmiennej .

Więc linker szczęśliwie łączy się z (zmienną) main i traktuje go jak funkcję sprawdzam. Ale dopiero gdy runtime system uruchomi globalny kod inicjalizacji zmiennej.

Kiedy testowałem próbkę, wydrukowałem ją, ale potem wywołało to seg-fault . Zakładam, że to wtedy system uruchomieniowy próbował wykonać zmienną int tak, jakby była to funkcja .

 7
Author: Galik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-29 19:07:02

Próbowałem tego na Win7 64bit OS używając VS2013 i kompiluje się poprawnie, ale kiedy próbuję zbudować aplikację dostaję ten komunikat z okna wyjścia.

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 ==========
 4
Author: Francis Cugler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-01 16:58:17

Wykonujesz tu trudną pracę. Jako main (jakoś) może być zadeklarowana jako liczba całkowita. Do wydruku wiadomości użyłeś operatora list, a następnie przypisz do niego 195. Jak powiedział ktoś poniżej, że z C++ nie jest komfortowo, to prawda. Ale ponieważ kompilator nie znalazł żadnej nazwy zdefiniowanej przez użytkownika, main, nie narzekał. Pamiętaj, że main nie jest funkcją zdefiniowaną przez system, jej funkcją zdefiniowaną przez użytkownika i rzeczą, od której program zaczyna wykonywać, jest Main Module, a nie main (). Ponownie main () jest wywoływany przez startup funkcja, która jest wykonywana przez loader celowo. Następnie wszystkie Twoje zmienne są inicjalizowane, & podczas inicjalizacji to wyjście w ten sposób. To wszystko. Program bez main() jest ok, ale nie Standardowy.

 -1
Author: Vikas.Ghode,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-06 11:39:03