Как может работать программа с глобальной переменной main, а не основная функция?

Рассмотрим следующую программу:

#include <iostream>
int main = ( std::cout << "C++ is excellent!\n", 195 ); 

Используя g++ 4.8.1 (mingw64) в ОС Windows 7, программа компилируется и работает нормально, печать:

С++ отлично!

на консоль. main представляется глобальной переменной, а не функцией; как эта программа может выполняться без функции main()? Соответствует ли этот код стандарту С++? Хорошо ли определено поведение программы? Я также использовал параметр -pedantic-errors, но программа все еще компилируется и запускается.

Ответ 1

Прежде чем переходить к вопросу о том, что происходит, важно указать, что программа плохо сформирована в соответствии с отчет о дефектах 1886: Языковая привязка для 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 года, и поэтому этот случай был совсем недавно явно плохо сформированным, что требует диагностики.

До этого кажется, что это будет undefined поведение, так как мы нарушаем требование стандарта С++ проекта из раздела 3.6.1 [basic.start.main]:

Программа должна содержать глобальную функцию main, которая является назначенным началом программы. [...]

Undefined поведение непредсказуемо и не требует диагностики. Непоследовательность, которую мы видим при воспроизведении поведения, типична для поведения undefined.

Итак, что действительно делает код и почему в некоторых случаях он дает результаты? Посмотрим, что у нас есть:

declarator  
|        initializer----------------------------------
|        |                                           |
v        v                                           v
int main = ( std::cout << "C++ is excellent!\n", 195 ); 
    ^      ^                                   ^
    |      |                                   |
    |      |                                   comma operator
    |      primary expression
global variable of type int

У нас есть main, который является объявленным в глобальном пространстве имен и инициализируется, переменная имеет статическую продолжительность хранения. Реализация определяется, будет ли инициализация выполняться до того, как будет выполнена попытка вызова main, но похоже, что gcc делает это до вызова main.

В коде используется оператор запятая, левый операнд - это отброшенное значение выражения и используется здесь исключительно для побочного эффекта вызова std::cout. Результатом оператора запятой является правый операнд, который в этом случае является prvalue 195, который присваивается переменной main.

Мы видим, что сергей указывает, сгенерированная сборка показывает, что во время статической инициализации вызывается cout. Хотя более интересный момент для обсуждения видеть живую сессию godbolt будет следующим:

main:
.zero   4

а затем:

movl    $195, main(%rip)

Вероятный сценарий заключается в том, что программа переходит к символу main, ожидая, что действительный код будет там, и в в некоторых случаях будет seg-fault. Поэтому, если это так, мы ожидаем, что сохранение допустимого машинного кода в переменной main может привести к работоспособной программе, если предположить, что мы находимся в сегменте, который позволяет выполнять код. Мы можем видеть в этой записи IOCCC 1984 года именно это.

Похоже, мы можем получить gcc для этого в C используя (посмотреть его в прямом эфире):

const int main = 195 ;

Это seg-faults, если переменная main не является const предположительно, потому что она не находится в исполняемом месте, Hat Tip к этому комментарию здесь, который дал мне эта идея.

Также см. FUZxxl ответьте здесь на C-специфическую версию этого вопроса.

Ответ 2

Из 3.6.1/1:

Программа должна содержать глобальную функцию main, которая является назначенный старт программы. Реализация определяется, является ли программа в автономной среде требуется для определения основного функция.

Из этого видно, что g++ позволяет программе (предположительно, как "автономное" предложение) без основной функции.

Тогда из 3.6.1/3:

Функция main не должна использоваться (3.2) внутри программы. Связь (3.5) основной определяется реализацией. Программа, которая объявляет, что основной является встроенным или статическим. Название main иначе не зарезервированы.

Итак, здесь мы узнаем, что отлично иметь целую переменную с именем main.

Наконец, если вам интересно, почему вывод печатается, инициализация int main использует оператор запятой для выполнения cout при статическом инициализации, а затем предоставляет фактическое целочисленное значение для инициализации.

Ответ 3

gcc 4.8.1 генерирует следующую сборку x86:

.LC0:
    .string "C++ is excellent!\n"
    subq    $8, %rsp    #,
    movl    std::__ioinit, %edi #,
    call    std::ios_base::Init::Init() #
    movl    $__dso_handle, %edx #,
    movl    std::__ioinit, %esi #,
    movl    std::ios_base::Init::~Init(), %edi  #,
    call    __cxa_atexit    #
    movl    $.LC0, %esi #,
    movl    std::cout, %edi #,
    call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)   #
    movl    $195, main(%rip)    #, main
    addq    $8, %rsp    #,
    ret
main:
    .zero   4

Обратите внимание, что cout вызывается во время инициализации, а не в функции main!

.zero 4 объявляет 4 (0-инициализированных) байта, начиная с местоположения main, где main - имя переменной [!].

Символ main интерпретируется как начало программы. Поведение зависит от платформы.

Ответ 4

Это плохо сформированная программа. Он сбой в моей тестовой среде, cygwin64/g++ 4.9.3.

Из стандарта:

3.6.1 Основная функция [basic.start.main]

1 Программа должна содержать глобальную функцию main, которая является назначенным началом программы.

Ответ 5

Причина, по которой я считаю, что это работает, заключается в том, что компилятор не знает, что он компилирует функцию main(), поэтому он компилирует глобальное целое число с побочными эффектами присваивания.

Формат объекта, скомпилированный этой единицей перевода, не способен различать символ функции и переменный символ.

Таким образом, компоновщик счастливо связывается с основным символом (переменной) и рассматривает его как вызов функции. Но только до тех пор, пока система выполнения не запустит код инициализации глобальной переменной.

Когда я запустил образец, он распечатался, но затем он вызвал ошибку seg. Я предполагаю, что когда система времени выполнения пыталась выполнить переменную int, как если бы она была функцией.

Ответ 6

Я пробовал это на 64-битной ОС Win7 с использованием VS2013, и он компилируется правильно, но когда я пытаюсь создать приложение, я получаю это сообщение из окна вывода.

1>------ Build started: Project: tempTest, Configuration: Debug Win32 ------
1>LINK : fatal error LNK1561: entry point must be defined
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

Ответ 7

Здесь вы делаете сложную работу. Как основной (каким-то образом) можно объявить как целое. Вы использовали оператор списка для печати сообщения, а затем присвоили ему 195. Как сказал кто-то ниже, что он не успокаивается с С++, это правда. Но поскольку компилятор не нашел ни одного определяемого пользователем имени, главное, он не стал жаловаться. Помните, что main не является функцией, определяемой системой, ее определяемой пользователем функцией и вещью, из которой запускается программа, является Main Module, а не main(), в частности. Опять же main() вызывается функцией запуска, которая намеренно выполняется загрузчиком. Затем все ваши переменные инициализируются, и при инициализации он выводится так. Это. Программа без main() в порядке, но не стандартная.