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 () :
[...] Program, który deklaruje zmienną main w zasięgu globalnym lub który deklaruje nazwę main z łączeniem języka C (w dowolnej przestrzeni nazw) jest źle sformułowany. […]
Najnowsze wersje clang i gcc powodują, że jest to błąd i program się nie skompiluje ( zobacz przykład na żywo z gcc ):
error: cannot declare '::main' to be a global variable
int main = ( std::cout << "C++ is excellent!\n", 195 );
^
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] :
Program powinien zawierać globalną funkcję zwaną main, która jest wyznaczonym początkiem programu. […]
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:
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ó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łaniem main
.
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 prvalue 195
przypisana do zmiennej main
.
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 :
main:
.zero 4
i kolejne:
movl $195, main(%rip)
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 zmiennej main
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 ):
const int main = 195 ;
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.