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 się i działa dobrze, drukując:
C ++ jest doskonały!
do konsoli. main
wydaje się być raczej zmienną globalną niż funkcją; jak ten program może działać bez tej funkcji main()
? Czy ten kod jest zgodny ze standardem C ++? Czy zachowanie programu jest dobrze zdefiniowane? Użyłem również tej -pedantic-errors
opcji, ale program nadal się kompiluje i działa.
c++
main
language-lawyer
Burzyciel
źródło
źródło
195
jest to kodRET
instrukcji i że w konwencji wywoływania C, wywołujący czyści stos.main()
funkcji? W rzeczywistości są one całkowicie niezwiązane.)int main = ( std::cout << "C++ is excellent!\n", exit(0),1 );
(i włączając<cstdlib>
), chociaż program pozostaje prawnie źle sformułowany.Odpowiedzi:
Zanim przejdziemy do sedna pytania o to, co się dzieje, ważne jest, aby zwrócić uwagę, że program jest źle sformułowany zgodnie z raportem defektu 1886: Powiązanie językowe dla main () :
Najnowsze wersje clang i gcc powodują, że jest to błąd i program się nie skompiluje ( zobacz przykład na żywo z gcc ):
Dlaczego więc nie było diagnostyki w starszych wersjach gcc i clang? Ten raport o wadzie nie miał nawet proponowanego rozwiązania aż do końca 2014 roku, więc ten przypadek dopiero niedawno został wyraźnie źle sformułowany, co wymaga diagnostyki.
Przed tym, wydaje się, że byłoby to niezdefiniowane zachowanie ponieważ jesteśmy naruszono powinny wymogiem projektu C ++ standard od sekcji
3.6.1
[basic.start.main] :Nieokreślone zachowanie jest nieprzewidywalne i nie wymaga diagnostyki. Niespójność, którą widzimy przy odtwarzaniu zachowania, jest typowym niezdefiniowanym zachowaniem.
Więc co właściwie robi kod i dlaczego w niektórych przypadkach daje wyniki? Zobaczmy, co mamy:
Mamy,
main
która jest int zadeklarowaną w globalnej przestrzeni nazw i jest inicjalizowana, zmienna ma statyczny czas trwania. W implementacji zdefiniowano, czy inicjalizacja nastąpi przed podjęciem próby wywołania,main
ale wygląda na to, że gcc robi to przed wywołaniemmain
.Kod używa operatora przecinka , lewy operand jest odrzuconym wyrażeniem wartości i jest tutaj używany wyłącznie jako efekt uboczny wywołania
std::cout
. Wynikiem operatora przecinka jest prawy operand, którym w tym przypadku jest prvalue195
przypisana do zmiennejmain
.Widzimy, że sergej wskazuje na wygenerowane zestawy, które
cout
są wywoływane podczas statycznej inicjalizacji. Chociaż bardziej interesującym punktem do dyskusji jest sesja godbolt na żywo :i kolejne:
Prawdopodobny scenariusz jest taki, że program przeskakuje do symbolu,
main
oczekując, że będzie tam obecny prawidłowy kod, aw niektórych przypadkach wystąpi seg-fault . Więc jeśli tak jest, spodziewalibyśmy się, że przechowywanie prawidłowego kodu maszynowego w zmiennejmain
może prowadzić do działającego programu , zakładając, że znajdujemy się w segmencie, który umożliwia wykonanie kodu. Widzimy, że wpis IOCCC z 1984 r. Właśnie to robi .Wygląda na to, że możemy zmusić gcc do zrobienia tego w C używając ( zobacz na żywo ):
To seg-faults, jeśli zmienna
main
nie jest przypuszczalnie const, ponieważ nie znajduje się w lokalizacji wykonywalnej, Hat Tip do tego komentarza, który dał mi ten pomysł.Zobacz także odpowiedź FUZxxl na konkretną wersję tego pytania w języku C.
źródło
main
nie jest zastrzeżonym identyfikatorem (3.6.1 / 3). W tym przypadku myślę, że obsługa tego przypadku przez VS2013 (patrz odpowiedź Francisa Cuglera) jest bardziej poprawna w obsłudze niż gcc & clang.Od 3.6.1 / 1:
Z tego wynika, że g ++ pozwala programowi (prawdopodobnie jako klauzula „wolnostojąca”) bez funkcji głównej.
Następnie od 3.6.1 / 3:
Tutaj dowiadujemy się, że posiadanie zmiennej całkowitej o nazwie jest w porządku
main
.Wreszcie, jeśli zastanawiasz się, dlaczego wyjście jest drukowane, inicjalizacja
int main
używa operatora przecinka do wykonaniacout
w statycznym init, a następnie dostarcza rzeczywistą wartość całkowitą do wykonania inicjalizacji.źródło
main
na coś innego:(.text+0x20): undefined reference to
główna ''gcc 4.8.1 generuje następujący zestaw x86:
Zauważ, że
cout
jest wywoływane podczas inicjalizacji, a nie wmain
funkcji!.zero 4
deklaruje 4 (zainicjowane przez 0) bajty, zaczynając od lokalizacjimain
, gdziemain
jest nazwą zmiennej [!] .main
Symbol jest interpretowana jako początek programu. Zachowanie zależy od platformy.źródło
195
jest kodem operacyjnym dlaret
niektórych architektur. Więc mówienie o zerowej instrukcji może nie być dokładne.To jest źle sformułowany program. Zawala się na moim środowisku testowym, cygwin64 / g ++ 4.9.3.
Ze standardu:
źródło
Uważam, że to działa, ponieważ kompilator nie wie, że kompiluje
main()
funkcję, więc kompiluje globalną liczbę całkowitą ze skutkami ubocznymi przypisania.Format obiektu , który to tłumaczenie-jednostka jest kompilowany do nie jest zdolne do różnicowania pomiędzy symbolem funkcyjnym oraz symbol zmienny .
Zatem linker szczęśliwie łączy się z głównym symbolem (zmiennej) i traktuje go jak wywołanie funkcji. Ale dopiero wtedy, gdy system wykonawczy uruchomi kod inicjujący zmienną globalną.
Kiedy sprawdziłem próbkę, została wydrukowana, ale potem spowodowała błąd seg . Zakładam, że wtedy system wykonawczy próbował wykonać zmienną int, jakby to była funkcja .
źródło
Wypróbowałem to na 64-bitowym systemie operacyjnym Win7 z VS2013 i kompiluje się poprawnie, ale kiedy próbuję zbudować aplikację, otrzymuję ten komunikat z okna wyjściowego.
źródło
main()
ponieważ jest to zmienna typuint
Wykonujesz tutaj trudną pracę. Jako main (w jakiś sposób) można zadeklarować, że jest liczbą całkowitą. Użyłeś operatora listy do wydrukowania wiadomości, a następnie przypisałeś jej 195. Jak powiedział ktoś poniżej, że nie jest to wygodne w C ++, jest prawdą. Ale ponieważ kompilator nie znalazł żadnej nazwy użytkownika, main, nie skarżył się. Pamiętaj, że main nie jest funkcją zdefiniowaną przez system, jego funkcją zdefiniowaną przez użytkownika i rzeczą, od której program zaczyna się wykonywać, jest moduł główny, a nie main (). Ponownie main () jest wywoływana przez funkcję startową, która jest celowo wykonywana przez program ładujący. Następnie wszystkie twoje zmienne są inicjalizowane, a podczas inicjalizacji wyjście w ten sposób. Otóż to. Program bez funkcji main () jest w porządku, ale nie jest standardowy.
źródło