Прежде чем перейти к сути вопроса о том, что происходит, важно указать, что программа плохо сформирована в соответствии с отчетом о дефекте 1886: Языковая привязка для main () :
[...] Программа, которая объявляет переменную main в глобальной области или объявляет имя main со связью на языке C (в любом пространстве имен), имеет неправильный формат. [...]
В самых последних версиях clang и gcc это вызывает ошибку, и программа не компилируется ( см. Пример gcc live ):
error: cannot declare '::main' to be a global variable
int main = ( std::cout << "C++ is excellent!\n", 195 );
^
Так почему же в старых версиях gcc и clang не было диагностики? В этом отчете о дефектах даже не было предложенного решения до конца 2014 года, поэтому этот случай совсем недавно был явно неправильно оформлен, что требует диагностики.
До этого, похоже, это было бы неопределенным поведением, поскольку мы нарушаем обязательное требование чернового варианта стандарта C ++ из раздела 3.6.1
[basic.start.main] :
Программа должна содержать глобальную функцию с именем main, которая является назначенным запуском программы. [...]
Неопределенное поведение непредсказуемо и не требует диагностики. Несоответствие, которое мы видим при воспроизведении поведения, является типичным неопределенным поведением.
Итак, что на самом деле делает код и почему в некоторых случаях он дает результаты? Посмотрим, что у нас есть:
declarator
| initializer----------------------------------
| | |
v v v
int main = ( std::cout << "C++ is excellent!\n", 195 );
^ ^ ^
| | |
| | comma operator
| primary expression
global variable of type int
У нас main
есть int, объявленный в глобальном пространстве имен и инициализируемый, переменная имеет статический срок хранения. Реализация определяет, будет ли инициализация выполняться до попытки вызова, main
но похоже, что gcc делает это до вызова main
.
В коде используется оператор запятой , левый операнд представляет собой выражение отброшенного значения и используется здесь исключительно для побочного эффекта вызова std::cout
. Результатом оператора запятой является правый операнд, который в данном случае является значением prvalue, 195
присвоенным переменной main
.
Мы видим, что сергей указывает на сгенерированную сборку, которая cout
вызывается во время статической инициализации. Хотя более интересным моментом для обсуждения см. Живую сессию Godbolt будет следующее:
main:
.zero 4
и последующие:
movl $195, main(%rip)
Вероятный сценарий состоит в том, что программа переходит к символу, main
ожидая присутствия действительного кода, и в некоторых случаях выполняет сегментарный сбой . Так что, если это так, мы ожидаем, что сохранение действительного машинного кода в переменной main
может привести к работоспособной программе , если мы находимся в сегменте, который позволяет выполнение кода. Мы видим, что эта запись IOCCC 1984 г. делает именно это .
Похоже, мы можем заставить gcc сделать это на C, используя ( см. Это вживую ):
const int main = 195 ;
Если переменная main
не является константой, то возникает ошибка сегментации, предположительно потому, что она не находится в исполняемом месте. Подсказка к этому комментарию здесь, который дал мне эту идею.
Также см. Ответ FUZxxl здесь на версию этого вопроса для C.