Почему объявление main как массива компилируется?
Я видел фрагмент кода на CodeGolf это предназначено как бомба компилятора, где main
объявляется как огромный массив. Я попробовал следующую (не бомба) версию:
int main[1] = { 0 };
Кажется, он отлично компилируется под Clang и только с предупреждением под GCC:
Внимание: 'main' обычно является функцией [- Wmain]
полученный двоичный файл, конечно, мусор.
но почему он вообще компилируется? Это даже разрешено спецификацией C? Раздел, который я считаю актуальным, говорит:
5.1.2.2.1 запуск программы
функция, вызываемая при запуске программы, называется главным. Реализация не объявляет прототип для этой функции. Он должен быть определен с возвращаемым типом int и без параметров [...] или с двумя параметрами [...] или каким-то другим способом, определенным реализацией.
включает ли "какой-либо другой способ реализации" глобальный массив? (Мне кажется, что спецификация все еще относится к функции.)
Если нет, то это расширение компилятора? Или особенность toolchains, которая служит какой-то другой цели, и они решили сделать ее доступной через интерфейс?
6 ответов:
это потому, что C позволяет использовать "не размещенную" или автономную среду, которая не требует . Это означает, что имя
main
освобождается для других целей. Вот почему язык как таковой допускает такие объявления. Большинство компиляторов предназначены для поддержки обоих (разница в основном заключается в том, как выполняется связывание), и поэтому они не запрещают конструкции, которые были бы незаконными в размещенной среде.раздел, на который вы ссылаетесь в стандарте, относится к размещенная среда, соответствующая для freestanding:
в автономной среде (в которой выполнение программы C может происходить без каких-либо преимущество операционной системы), имя и тип функции, вызываемой у программы стартап-это реализация-определено. Любые библиотечные помещения, доступные для автономного программы, отличные от минимального набора, требуемого пунктом 4, определяются реализацией.
если вы связать его, как обычно это будет плохо, так как компоновщик обычно имеет мало знаний о природе символов (какой тип он имеет или даже если это функция или переменная). В этом случае компоновщик с радостью разрешит вызовы
main
переменной с именемmain
. Если символ не найден, это приведет к ошибке ссылку.если вы связываете его как обычно, вы в основном пытаетесь использовать компилятор в размещенной операции, а затем не определяете
main
Как вы должны означает неопределенное поведение, как согласно приложению J. 2:поведение не определено в следующих обстоятельствах:
- ...
- программа в размещенной среде не определяет функцию с именем главный использование одного указанных форм (5.1.2.2.1)
цель автономной возможности состоит в том, чтобы иметь возможность использовать C в средах, где (например) стандартные библиотеки или инициализация CRT не заданы. Это означает, что код, который выполняется перед
main
вызывается (это инициализация CRT, которая инициализирует среду выполнения C) может не предоставляться, и вы должны были бы предоставить это сами (и вы можете решить иметьmain
или может не).
если вам интересно, как создать программу в основном массиве:https://jroweboy.github.io/c/asm/2015/01/26/when-is-main-not-a-function.html. пример источника там просто содержит массив char (а позже int) с именем
main
который заполнен машинными инструкциями.основные шаги и проблемы были:
- получить машинные инструкции основной функции из дампа памяти gdb и скопировать его в массив
- тег данные в
main[]
исполняемый файл, объявив его const (данные, по-видимому, либо записываются, либо исполняются)- последняя деталь: измените адрес для фактических строковых данных.
полученный код C просто
const int main[] = { -443987883, 440, 113408, -1922629632, 4149, 899584, 84869120, 15544, 266023168, 1818576901, 1461743468, 1684828783, -1017312735 };
но результаты в исполняемой программе на 64-битном ПК:
$ gcc -Wall final_array.c -o sixth final_array.c:1:11: warning: ‘main’ is usually a function [-Wmain] const int main[] = { ^ $ ./sixth Hello World!
main
is-после компиляции-просто еще один символ в объектном файле, как и многие другие (глобальные функции, глобальные переменные и т. д.).компоновщик свяжет символ
main
независимо от его типа. Действительно, компоновщик вообще не может видеть тип символа (он можете смотрите, что это не в.text
- раздел однако, но ему все равно;))используя gcc, стандартная точка входа _start, которая в свою очередь вызывает main () после подготовки среда выполнения. Таким образом, он перейдет к адресу целочисленного массива, что обычно приведет к плохой инструкции, segfault или некоторому другому плохому поведению.
все это, конечно, не имеет ничего общего с C-стандартом.
проблема в том, что
main
не является зарезервированным идентификатором. Стандарт C говорит только о том, что в размещенных системах обычно есть функция, называемая main. Но ничто в стандарте не мешает вам злоупотреблять тем же идентификатором для других зловещих целей.GCC дает вам самодовольное предупреждение "main обычно является функцией", намекая, что использование идентификатора
main
для других целей-это не гениальная идея.
глупо пример:
#include <stdio.h> int main (void) { int main = 5; main: printf("%d\n", main); main--; if(main) { goto main; } else { int main (void); main(); } }
эта программа будет повторно печатать числа 5,4,3,2,1, пока не получит переполнение стека и сбои (не пытайтесь сделать это дома). К сожалению, приведенная выше программа является строго соответствующей программой C, и компилятор не может остановить вас от ее написания.
он компилируется только потому, что вы не используете правильные параметры (и работает, потому что компоновщики иногда заботятся только о имена символов, а не их тип).
$ gcc -std=c89 -pedantic -Wall x.c x.c:1:5: warning: ISO C forbids zero-size array ‘main’ [-Wpedantic] int main[0]; ^ x.c:1:5: warning: ‘main’ is usually a function [-Wmain]