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, mainktó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, mainale 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 195przypisana do zmiennej main.
Widzimy, że sergej wskazuje na wygenerowane zestawy, które coutsą 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, mainoczekują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 mainmoż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 mainnie 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.