Рассмотрим следующую программу:
#include <iostream>
int main = ( std::cout << "C++ is excellent!\n", 195 );
Используя g ++ 4.8.1 (mingw64) в ОС Windows 7, программа компилируется и работает нормально, печатая:
C ++ отличный!
к консоли. main
выглядит как глобальная переменная, а не функция; как эта программа может работать без функции main()
? Соответствует ли этот код стандарту C ++? Хорошо ли определено поведение программы? Я тоже использовал эту -pedantic-errors
опцию, но программа все еще компилируется и запускается.
c++
main
language-lawyer
Деструктор
источник
источник
195
это код операции дляRET
инструкции и что в соглашении о вызовах C вызывающий объект очищает стек.main()
функции? На самом деле, они совершенно не связаны.)int main = ( std::cout << "C++ is excellent!\n", exit(0),1 );
(и в том числе<cstdlib>
), хотя программа остается юридически плохо оформленной.Ответы:
Прежде чем перейти к сути вопроса о том, что происходит, важно указать, что программа плохо сформирована в соответствии с отчетом о дефекте 1886: Языковая привязка для main () :
В самых последних версиях clang и gcc это вызывает ошибку, и программа не компилируется ( см. Пример gcc live ):
Так почему же в старых версиях gcc и clang не было диагностики? В этом отчете о дефектах даже не было предложенного решения до конца 2014 года, поэтому этот случай совсем недавно был явно неправильно оформлен, что требует диагностики.
До этого, похоже, это было бы неопределенным поведением, поскольку мы нарушаем обязательное требование чернового варианта стандарта C ++ из раздела
3.6.1
[basic.start.main] :Неопределенное поведение непредсказуемо и не требует диагностики. Несоответствие, которое мы видим при воспроизведении поведения, является типичным неопределенным поведением.
Итак, что на самом деле делает код и почему в некоторых случаях он дает результаты? Посмотрим, что у нас есть:
У нас
main
есть int, объявленный в глобальном пространстве имен и инициализируемый, переменная имеет статический срок хранения. Реализация определяет, будет ли инициализация выполняться до попытки вызова,main
но похоже, что gcc делает это до вызоваmain
.В коде используется оператор запятой , левый операнд представляет собой выражение отброшенного значения и используется здесь исключительно для побочного эффекта вызова
std::cout
. Результатом оператора запятой является правый операнд, который в данном случае является значением prvalue,195
присвоенным переменнойmain
.Мы видим, что сергей указывает на сгенерированную сборку, которая
cout
вызывается во время статической инициализации. Хотя более интересным моментом для обсуждения см. Живую сессию Godbolt будет следующее:и последующие:
Вероятный сценарий состоит в том, что программа переходит к символу,
main
ожидая присутствия действительного кода, и в некоторых случаях выполняет сегментарный сбой . Так что, если это так, мы ожидаем, что сохранение действительного машинного кода в переменнойmain
может привести к работоспособной программе , если мы находимся в сегменте, который позволяет выполнение кода. Мы видим, что эта запись IOCCC 1984 г. делает именно это .Похоже, мы можем заставить gcc сделать это на C, используя ( см. Это вживую ):
Если переменная
main
не является константой, то возникает ошибка сегментации, предположительно потому, что она не находится в исполняемом месте. Подсказка к этому комментарию здесь, который дал мне эту идею.Также см. Ответ FUZxxl здесь на версию этого вопроса для C.
источник
main
это не зарезервированный идентификатор (3.6.1 / 3). В этом случае я думаю, что обработка этого случая VS2013 (см. Ответ Фрэнсиса Куглера) более правильна в этом отношении, чем gcc & clang.Начиная с 3.6.1 / 1:
Отсюда похоже, что g ++ позволяет программе (предположительно, как "автономное" предложение) без основной функции.
Тогда из 3.6.1 / 3:
Итак, здесь мы узнаем, что иметь целочисленную переменную совершенно нормально
main
.Наконец, если вам интересно, почему вывод выводится на печать, при инициализации
int main
используется оператор запятой для выполненияcout
в статической инициализации, а затем предоставляется фактическое целое значение для инициализации.источник
main
что-то другое:(.text+0x20): undefined reference to
main ''gcc 4.8.1 генерирует следующую сборку x86:
Обратите внимание:
cout
вызывается во время инициализации, а не вmain
функции!.zero 4
объявляет 4 (инициализированных 0) байта, начиная с местоположенияmain
, гдеmain
- имя переменной [!] .main
Символ интерпретируется как начало программы. Поведение зависит от платформы.источник
195
это код операции дляret
некоторых архитектур. Таким образом, утверждение нулевых инструкций может быть неточным.Это плохо сформированная программа. Он вылетает в моей тестовой среде cygwin64 / g ++ 4.9.3.
Из стандарта:
источник
Я считаю, что это работает, потому что компилятор не знает, что он компилирует
main()
функцию, поэтому он компилирует глобальное целое число с побочными эффектами присваивания.Формат объекта, в который компилируется эта единица трансляции, не позволяет различать функциональный символ и переменный символ .
Таким образом, компоновщик с радостью связывает главный символ (переменной) и рассматривает его как вызов функции. Но не раньше, чем исполняющая система выполнит код инициализации глобальной переменной.
Когда я запустил образец, он распечатал, но затем это вызвало ошибку сегмента . Я предполагаю, что это когда система времени выполнения пыталась выполнить переменную типа int, как если бы это была функция .
источник
Я пробовал это на 64-битной ОС Win7 с использованием VS2013, и он компилируется правильно, но когда я пытаюсь создать приложение, я получаю это сообщение из окна вывода.
источник
main()
потому что это переменная типаint
Вы здесь делаете хитрую работу. Поскольку main (каким-то образом) можно было объявить целым числом. Вы использовали оператор списка, чтобы распечатать сообщение, а затем назначили ему 195. Как сказал кто-то ниже, то, что C ++ не устраивает, это правда. Но поскольку компилятор не нашел ни одного пользовательского имени main, он не пожаловался. Помните, что main - это не функция, определяемая системой, ее определяемая пользователем функция и объект, с которого начинается выполнение программы, - это основной модуль, а не main (), в частности. Снова main () вызывается функцией запуска, которая преднамеренно выполняется загрузчиком. Затем все ваши переменные инициализируются и при инициализации выводятся так. Вот и все. Программа без main () нормальная, но не стандартная.
источник