Это int main; действующая программа на C / C ++?

113

Я спрашиваю, потому что мой компилятор, кажется, так думает, хотя я этого не делаю.

echo 'int main;' | cc -x c - -Wall
echo 'int main;' | c++ -x c++ - -Wall

Clang не выдает никаких предупреждений или ошибок при этом, а gcc выдает только кроткое предупреждение:, 'main' is usually a function [-Wmain]но только при компиляции как C. Указание a -std=не имеет значения.

В противном случае он компилируется и линкуется нормально. Но при выполнении он немедленно завершается SIGBUS(для меня).

Чтение (отличных) ответов на вопрос, что должно возвращать main () в C и C ++? и беглый просмотр спецификаций языка, мне определенно может показаться , что требуется функция main . Но словоблудие из gcc -Wmain('main' - обычно функция) (и недостаток ошибок здесь), кажется, вероятно, предполагает иное.

Но почему? Есть ли для этого какое-то странное крайнее или «историческое» применение? Кто-нибудь знает, что дает?

Я полагаю, что я действительно считаю, что это должно быть ошибкой в размещенной среде, а?

Джефф Никсон
источник
6
Чтобы сделать gcc компилятором (в основном) стандартным, вам нуженgcc -std=c99 -pedantic ...
pmg
3
@pmg Это то же самое предупреждение, с -pedanticлюбым или без него -std. Моя система c99также компилирует это без предупреждений или ошибок ...
Джефф Никсон
3
К сожалению, если вы «достаточно умны», вы можете создавать вещи, которые приемлемы для компилятора, но не имеют смысла. В этом случае вы связываете библиотеку времени выполнения C для вызова вызываемой переменной main, что вряд ли сработает. Если вы инициализируете main с «правильным» значением, он может действительно вернуть ...
Матс Петерссон
7
И даже если он действителен, это ужасная вещь (нечитаемый код). Кстати, это может отличаться в размещенных реализациях и в автономных реализациях (о которых не известно main)
Базиль Старынкевич
1
Чтобы получить больше удовольствия, попробуйтеmain=195;
imallett

Ответы:

97

Поскольку вопрос помечен двойными тегами как C и C ++, рассуждения для C ++ и C будут разными:

  • C ++ использует изменение имен, чтобы помочь компоновщику различать текстуально идентичные символы разных типов, например глобальную переменную xyzи отдельную глобальную функцию xyz(int). Однако имя mainникогда не искажается.
  • C не использует искажение, поэтому программа может запутать компоновщик, предоставив символ одного вида вместо другого символа, и программа будет успешно компоноваться.

Вот что здесь происходит: компоновщик ожидает найти символ main, и он это делает. Он «связывает» этот символ, как будто это функция, потому что он ничего не знает. Часть библиотеки времени выполнения, которая передает управление mainзапрашивающему компоновщику main, поэтому компоновщик предоставляет ему символ main, позволяя завершиться фазе компоновки. Конечно, это не удается во время выполнения, потому что mainэто не функция.

Вот еще одна иллюстрация той же проблемы:

файл xc:

#include <stdio.h>
int foo(); // <<== main() expects this
int main(){
    printf("%p\n", (void*)&foo);
    return 0;
}

файл yc:

int foo; // <<== external definition supplies a symbol of a wrong kind

компиляции:

gcc x.c y.c

Это компилируется и, вероятно, будет работать, но это поведение undefined, потому что тип символа, обещанного компилятору, отличается от фактического символа, предоставленного компоновщику.

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

dasblinkenlight
источник
3
Однако компилятор C ++ по-разному трактует основную функцию. Его имя не искажается даже без внешней буквы "C". Я предполагаю, что это потому, что в противном случае ему нужно было бы испустить свой собственный extern "C" main, чтобы гарантировать связывание.
UldisK 05
@UldisK Да, я сам это заметил и нашел довольно интересным. В этом есть смысл, но я никогда об этом не думал.
Джефф Никсон
2
На самом деле, результаты для C ++ и C не отличаются, как указано здесь - mainне подлежат искажению имени (как кажется) в C ++, независимо от того, является ли это функцией.
Джефф Никсон
4
@nm Я думаю, что ваша интерпретация вопроса слишком узка: помимо вопроса в заголовке сообщения OP явно пытается объяснить, почему его программа была скомпилирована в первую очередь ("мой компилятор, кажется, так думает, хотя я не знаю "), а также предложение о том, почему может быть полезно определить mainчто-либо, кроме функции. Ответ предлагает объяснение обеих частей.
dasblinkenlight 05
1
То, что основной символ не подлежит изменению имени, не имеет значения. В стандарте C ++ нет упоминания об изменении имени. Изменение имени - это проблема реализации.
Дэвид Хаммен
30

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

Конечно:

  • предупреждение полезно, поскольку оно весьма подвержено ошибкам;
  • у вас может быть исходный файл без main()функции (библиотеки).

РЕДАКТИРОВАТЬ

Некоторые ссылки:

  • main не является зарезервированным словом (C ++ 11):

    Функцию mainнельзя использовать в программе. Связь (3.5) mainопределяется реализацией. Программа , которая определяет главное как удаленные или который объявляет главное , чтобы быть inline, staticили constexprплохо сформированные. Имя mainне зарезервировано. [Пример: могут вызываться функции-члены, классы и перечисления main, а также сущности в других пространствах имен. - конец примера]

    C ++ 11 - [basic.start.main] 3.6.1.3

    [2.11 / 3] [...] некоторые идентификаторы зарезервированы для использования реализациями C ++ и стандартными библиотеками (17.6.4.3.2) и не должны использоваться иначе; Диагностика не требуется.

    [17.6.4.3.2 / 1] Определенные наборы имен и сигнатур функций всегда зарезервированы для реализации:

    • Каждое имя, содержащее двойное подчеркивание __ или начинающееся с подчеркивания, за которым следует заглавная буква (2.12), зарезервировано для реализации для любого использования.
    • Каждое имя, начинающееся с подчеркивания, зарезервировано для реализации для использования в качестве имени в глобальном пространстве имен.
  • Зарезервированные слова в языках программирования .

    Зарезервированные слова не могут быть переопределены программистом, но предопределенные часто могут быть переопределены в некоторой степени. Это случай main: существуют области, в которых объявление, использующее этот идентификатор, переопределяет его значение.

Manlio
источник
- Я предполагаю , что я довольно обманут тем , что (как это будет так подвержен ошибкам), почему это предупреждение (не ошибка), и почему это только предупреждение , когда скомпилирован как C - Конечно, вы можете скомпилировать без main()функция, но вы не можете связать его как программу. То , что здесь происходит то , что программа «действует» в настоящее время связана без main(), просто main.
Джефф Никсон
7
cinи endlне находятся в пространстве имен по умолчанию - они находятся в stdпространстве имен. nposявляется членом std::basic_string.
AnotherParker 05
1
main будет зарезервирован в качестве глобального имени. Ни одна из других вещей, которые вы упомянули, не mainпредопределена.
Potatoswatter
1
См. C ++ 14 §3.6.1 и C11 §5.1.2.2.1 для ограничений того, что mainразрешено. C ++ говорит: «Реализация не должна предопределять основную функцию», а C говорит: «Реализация не объявляет прототип для этой функции».
Potatoswatter
@manlio: поясните, пожалуйста, что вы цитируете. Что касается простого C, цитаты неверны. Полагаю, это какой-то из стандартов С ++, не так ли?
dhein
19

Подходит ли программа int main;на C / C ++?

Не совсем понятно, что такое программа на C / C ++.

Подходит int main;ли программа C?

Да. Такая программа допускается в автономном исполнении. mainне должно иметь особого значения в отдельно стоящей среде.

Это недопустимо в размещенной среде.

Это int main;действующая программа на C ++?

То же самое.

Почему вылетает?

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

Почему компилятор меня предупреждает?

Компилятор может предупреждать вас обо всем, что ему заблагорассудится, если только он не отклоняет соответствующие программы. С другой стороны, предупреждение - это все, что требуется для диагностики несоответствующей программы. Поскольку эта единица трансляции не может быть частью действующей размещенной программы, диагностическое сообщение оправдано.

Это gccавтономная среда или размещенная среда?

Да.

gccдокументирует -ffreestandingфлаг компиляции. Добавьте его, и предупреждение исчезнет. Вы можете использовать его при сборке, например, ядер или прошивки.

g++не документирует такой флаг. Поставка, похоже, не влияет на эту программу. Вероятно, можно с уверенностью предположить, что среда, предоставляемая g ++, размещена. Отсутствие диагностики в этом случае - ошибка.

п. 'местоимения' м.
источник
17

Это предупреждение, поскольку технически это не запрещено. Код запуска будет использовать расположение символа «main» и перейдет к нему с тремя стандартными аргументами (argc, argv и envp). Это не так, и во время компоновки не может проверить, действительно ли это функция, или даже что у нее есть эти аргументы. Вот почему работает int main (int argc, char ** argv) - компилятор не знает об аргументе envp, и он просто не используется, и это очистка вызывающего.

В шутку можно сделать что-нибудь вроде

int main = 0xCBCBCBCB;

на машине x86 и, игнорируя предупреждения и тому подобное, он не просто компилируется, но и работает.

Кто-то использовал похожую технику для написания исполняемого файла (вроде того), который работает напрямую на нескольких архитектурах - http://phrack.org/issues/57/17.html#article . Он также был использован для победы в IOCCC - http://www.ioccc.org/1984/mullender/mullender.c .

нарядный
источник
1
«Это предупреждение, так как это технически не запрещено» - это недопустимо в C ++.
Приветствия и hth. - Alf
3
«три стандартных аргумента (argc, argv и envp)» - здесь, возможно, вы говорите о стандарте Posix.
Приветствия и hth. - Alf
В моей системе (Ubuntu 14 / x64) с gcc работает следующая строка:int main __attribute__ ((section (".text")))= 0xC3C3C3C3;
csharpfolk,
@ Cheersandhth.-Alf Первые два - стандартные, третий - POSIX.
dascandy
9

Это действующая программа?

Нет.

Это не программа, поскольку в ней нет исполняемых частей.

Допустимо ли компилировать?

Да.

Можно ли его использовать с действующей программой?

Да.

Не весь скомпилированный код должен быть исполняемым, чтобы быть действительным. Примерами являются статические и динамические библиотеки.

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

Это должно быть ошибкой?

Традиционно C ++ позволяет пользователю делать вещи, которые могут показаться бесполезными, но которые соответствуют синтаксису языка.

Я имею в виду, конечно, это можно было бы переклассифицировать как ошибку, но почему? Какой цели это могло бы служить, если предупреждение не служило?

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

Майкл Газонда
источник
Он создает видимый извне символ с именем main. Как действительная программа, которая должна быть внешне видимая функция с именем main, ссылка на него?
Кейт Томпсон
@KeithThompson Загрузить во время выполнения. Поясню.
Майкл Газонда 05
Это возможно, потому что он не может различить типы символов. Связывание работает нормально - выполнение (кроме тщательно продуманного случая) - нет.
Крис Стрэттон
1
@ChrisStratton: Я думаю, что аргумент Кейта состоит в том, что связывание не удается, потому что символ определяется множеством ... потому что «действительная программа» не будет действительной программой, если она не определяет mainфункцию.
Ben Voigt
@BenVoigt Но если он появляется в библиотеке, то компоновка не будет (и, вероятно, не может) потерпеть неудачу, потому что во время компоновки программы int main;определение не будет видно.
6

Я хотел бы добавить к уже полученным ответам, сославшись на действующие языковые стандарты.

Это int main; действующая программа на C?

Краткий ответ (мое мнение): только если в вашей реализации используется «автономная среда выполнения».

Все следующие цитаты из C11

5. Окружающая среда

Реализация переводит исходные файлы C и выполняет программы на C в двух средах системы обработки данных, которые будут называться средой перевода и средой выполнения [...]

5.1.2 Среда выполнения

Определены две среды выполнения: автономная и размещенная. В обоих случаях запуск программы происходит, когда среда выполнения вызывает назначенную функцию C.

5.1.2.1 Отдельностоящая среда

В автономной среде (в которой выполнение программы C может происходить без каких-либо преимуществ операционной системы) имя и тип функции, вызываемой при запуске программы, определяются реализацией.

5.1.2.2 Размещенная среда

Размещенная среда не требуется, но она должна соответствовать следующим спецификациям, если они есть.

5.1.2.2.1 Запуск программы

Функция, вызываемая при запуске программы, называется main . [...] Он должен быть определен с возвращаемым типом int и без параметров [...], или с двумя параметрами [...], или эквивалентными, или каким-либо другим способом, определяемым реализацией.

Из них наблюдается следующее:

  • Программа C11 может иметь автономную или размещенную среду выполнения и быть действительной.
  • Если у него есть отдельно стоящий, не обязательно наличие основной функции.
  • В противном случае должен быть такой, у которого значение возврата типа int .

В автономной среде выполнения я бы сказал, что это допустимая программа, которая не позволяет запускать, потому что для этого нет функции, требуемой в 5.1.2. В размещенной среде выполнения, хотя ваш код представляет объект с именем main , он не может предоставить возвращаемое значение, поэтому я бы сказал, что это недопустимая программа в этом смысле, хотя можно было бы также утверждать, как раньше, если программа не предназначено для выполнения (например, может потребоваться предоставить данные), тогда это просто не позволяет сделать это.

Это int main; действующая программа на C ++?

Краткий ответ (мое мнение): только если в вашей реализации используется «автономная среда выполнения».

Цитата из C ++ 14

3.6.1 Основная функция

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

Здесь, в отличие от стандарта C11, меньше ограничений применяется к автономной среде выполнения, поскольку функция запуска не упоминается вообще, в то время как для размещенной среды выполнения ситуация в значительной степени такая же, как для C11.

Опять же, я бы сказал, что для размещенного случая ваш код не является допустимой программой на C ++ 14, но я уверен, что он предназначен для автономного случая.

Поскольку в моем ответе рассматривается только среда выполнения , я думаю, что ответ dasblinkenlicht вступает в игру, так как изменение имени происходит в среде перевода заранее. Здесь я не уверен, что приведенные выше цитаты соблюдаются так строго.

Инго Шалк-Шупп
источник
4

Я полагаю, что я действительно считаю, что это должно быть ошибкой в ​​размещенной среде, а?

Ошибка твоя. Вы не указали функцию с именем, mainкоторая возвращает, intи пытались использовать вашу программу в размещенной среде.

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

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

Все в порядке, если вы используете только первую единицу компиляции в автономной среде и только вторую в размещенной среде. Что, если вы используете оба в одной программе? В C ++ вы нарушили одно правило определения. Это неопределенное поведение. В C вы нарушили правило, согласно которому все ссылки на один символ должны быть согласованными; если нет, это неопределенное поведение. Неопределенное поведение - это «выйди из тюрьмы, бесплатно!» карточка разработчикам реализации. Все, что делает реализация в ответ на неопределенное поведение, соответствует стандарту. Реализация не должна предупреждать, не говоря уже об обнаружении неопределенного поведения.

Что, если вы используете только одну из этих единиц компиляции, но используете неправильную (что вы и сделали)? В C ситуация ясна. Неспособность определить функцию mainв одной из двух стандартных форм в размещенной среде является неопределенным поведением. Предположим, вы вообще не определили main. Компилятор / компоновщик ничего не говорит об этой ошибке. То, что они действительно жалуются, - это их вежливость. То, что программа на C скомпилирована и скомпилирована без ошибок, является вашей ошибкой, а не компилятором.

В C ++ это немного менее ясно, потому что неспособность определить функцию mainв размещенной среде является ошибкой, а не неопределенным поведением (другими словами, это должно быть диагностировано). Однако одно правило определения в C ++ означает, что компоновщики могут быть довольно тупыми. Работа компоновщика заключается в разрешении внешних ссылок, и благодаря единому правилу определения компоновщик не должен знать, что означают эти символы. Вы предоставили символ с именем main, компоновщик ожидает увидеть символ с именем main, так что с компоновщиком все хорошо.

Дэвид Хаммен
источник
4

Для C пока это поведение, определяемое реализацией.

Как сказано в ISO / IEC9899:

5.1.2.2.1 Запуск программы

1 Функция, вызываемая при запуске программы, называется main. Реализация не объявляет прототипа для этой функции. Он должен быть определен с возвращаемым типом int и без параметров:

int main(void) { /* ... */ }

или с двумя параметрами (называемыми здесь argc и argv, хотя можно использовать любые имена, поскольку они являются локальными для функции, в которой они объявлены):

int main(int argc, char *argv[]) { /* ... */ }

или эквивалент; или каким-либо другим способом, определяемым реализацией.

dhein
источник
3

Нет, это недействительная программа.

Для C ++ это было недавно явно сделано неправильно в отчете о дефекте 1886: Языковая привязка для main (), в котором говорится:

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

и часть постановления включала следующее изменение:

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

Мы можем найти эту формулировку в последнем проекте стандарта C ++ N4527, который представляет собой проект C ++ 1z.

В последних версиях clang и gcc теперь возникает ошибка ( см. Вживую ):

error: main cannot be declared as global variable
int main;
^

До этого отчета о дефектах это было неопределенное поведение, не требующее диагностики. С другой стороны, плохо сформированный код требует диагностики, компилятор может сделать это либо предупреждением, либо ошибкой.

Шафик Ягмур
источник
Спасибо за обновление! Приятно видеть, что теперь это подхватывается диагностикой компилятора. Однако должен сказать, что нахожу изменения в стандарте C ++ в недоумении. (Для справки см. Комментарии выше относительно изменения имени main().) Я понимаю причины запрета на main()наличие явной спецификации связывания, но не понимаю, что это требует main()наличия связи C ++ . Конечно , стандарт не непосредственно адрес , как обращаться с ABI связь / имя коверкая, но на практике (скажем, с Itanium ABI) это будет искажать main()к _Z4mainv. Что мне не хватает?
Джефф Никсон,
Я думаю, что комментарий supercat покрывает это. Если реализация выполняет свои собственные функции перед вызовом определяемой пользователем main, тогда она может легко выбрать вместо этого вызов искаженного имени.
Шафик Ягмур,