Как работает метод main() в C?
Я знаю, что для записи основного метода есть две разные подписи -
int main()
{
//Code
}
или для обработки аргумента командной строки, мы пишем его как -
int main(int argc, char * argv[])
{
//code
}
В C++
Я знаю, что мы можем перегрузить метод, но в C
, как компилятор обрабатывает эти две разные подписи функции main
?
Ответы
Ответ 1
Некоторые из функций языка C начинаются как хаки, которые только что сработали.
Одной из этих функций является несколько подписей для основного, а также списков аргументов переменной длины.
Программисты заметили, что они могут передавать дополнительные аргументы функции, и с их компилятором ничего плохого не происходит.
Это так, если вызывающие соглашения таковы, что:
- Вызывающая функция очищает аргументы.
- Самые левые аргументы ближе к вершине стека или к базе фрейма стека, так что ложные аргументы не делают недействительной адресацию.
Один набор условных вызовов, который подчиняется этим правилам, является передачей параметров на основе стека, в результате чего вызывающий пользователь выдает аргументы, и они помещаются справа налево:
;; pseudo-assembly-language
;; main(argc, argv, envp); call
push envp ;; rightmost argument
push argv ;;
push argc ;; leftmost argument ends up on top of stack
call main
pop ;; caller cleans up
pop
pop
В компиляторах, где этот тип соглашения о вызове имеет значение, ничего особого не нужно делать для поддержки двух типов main
или даже дополнительных типов. main
может быть функцией без аргументов, и в этом случае он не обращает внимания на элементы, которые были перенесены в стек. Если это функция из двух аргументов, она находит argc
и argv
в качестве двух верхних элементов стека. Если это вариант с тремя аргументами, ориентированный на платформу, с указателем среды (общим расширением), это тоже будет работать: он найдет третий аргумент как третий элемент из верхней части стека.
И поэтому фиксированный вызов работает для всех случаев, позволяя связать один, фиксированный модуль запуска с программой. Этот модуль может быть записан на C, как функция, напоминающая это:
/* I'm adding envp to show that even a popular platform-specific variant
can be handled. */
extern int main(int argc, char **argv, char **envp);
void __start(void)
{
/* This is the real startup function for the executable.
It performs a bunch of library initialization. */
/* ... */
/* And then: */
exit(main(argc_from_somewhere, argv_from_somewhere, envp_from_somewhere));
}
Другими словами, этот начальный модуль всегда вызывает основной аргумент с тремя аргументами. Если main не принимает никаких аргументов или только int, char **
, он работает нормально, а также если он не принимает никаких аргументов из-за соглашений о вызовах.
Если бы вы делали такие вещи в своей программе, это было бы непереносимо и считалось бы поведением undefined по ISO C: объявлением и вызовом функции одним способом и определением ее в другой. Но трюк запуска компилятора не должен быть переносимым; он не руководствуется правилами для переносных программ.
Но предположим, что вызывающие соглашения таковы, что они не могут работать таким образом. В этом случае компилятор должен обрабатывать main
специально. Когда он замечает, что он компилирует функцию main
, он может генерировать код, который совместим, например, с тремя аргументами.
То есть вы пишете это:
int main(void)
{
/* ... */
}
Но когда компилятор видит это, он, по сути, выполняет преобразование кода, так что функция, которую он компилирует, выглядит примерно так:
int main(int __argc_ignore, char **__argv_ignore, char **__envp_ignore)
{
/* ... */
}
за исключением того, что имена __argc_ignore
не существуют буквально. Такие имена не вводятся в вашу область действия, и никаких предупреждений о неиспользуемых аргументах не будет.
Преобразование кода заставляет компилятор испускать код с правильной связью, которая знает, что ему нужно очистить три аргумента.
Другая стратегия реализации для компилятора или, возможно, линкера для пользовательской генерации функции __start
(или того, что она называется), или, по крайней мере, выбрать один из нескольких предварительно скомпилированных альтернатив. В объектном файле может храниться информация о том, какая из поддерживаемых форм main
используется. Компонент может посмотреть эту информацию и выбрать правильную версию модуля запуска, которая содержит вызов main
, который совместим с определением программы. В реализациях C обычно имеется только небольшое количество поддерживаемых форм main
, поэтому этот подход возможен.
Компиляторы для языка C99 всегда должны в некоторой степени относиться к main
, чтобы поддерживать хак, что если функция завершается без оператора return
, поведение выглядит так, как если бы выполнялось return 0
. Это, опять же, можно рассматривать с помощью преобразования кода. Компилятор замечает, что скомпилирована функция с именем main
. Затем он проверяет, может ли конец тела потенциально достижим. Если это так, он вставляет return 0;
Ответ 2
Нет никакой перегрузки main
даже в С++. Основная функция - это точка входа для программы, и должно существовать только одно определение.
Для стандартного C
Для размещенной среды (обычной), стандарт C99 говорит:
5.1.2.2.1 Запуск программы
Функция, вызванная при запуске программы, называется main
. Реализация не объявляет прототипа для этой функции. Это должно быть определенный с типом возврата int
и без параметров:
int main(void) { /* ... */ }
или с двумя параметрами (называемыми здесь argc
и argv
, хотя любые имена могут использоваться, поскольку они являются локальными для функции, в которой они объявляются):
int main(int argc, char *argv[]) { /* ... */ }
или эквивалент; 9) или каким-либо другим способом реализации.
9) Таким образом, int
можно заменить на имя typedef, определенное как int
, или тип argv
можно записать как char **argv
, и и так далее.
Для стандартного С++:
3.6.1 Основная функция [basic.start.main]
1 Программа должна содержать глобальную функцию main, которая является назначенным началом программы. [...]
2 Реализация не должна предопределять основную функцию. Эта функция не должна быть перегружена. Он должен имеют тип возвращаемого типа int, но в противном случае его тип определяется реализацией. Все реализации должны допускать оба следующих определения main:
int main() { /* ... */ }
и
int main(int argc, char* argv[]) { /* ... */ }
В стандарте С++ явно говорится: "Он [основная функция] должен иметь тип возвращаемого типа int, но в противном случае его тип определяется реализацией" и требует тех же двух сигнатур, что и стандарт C.
В размещенной среде (среда C, которая также поддерживает библиотеки C) - операционная система вызывает main
.
В не-размещенной среде (один предназначен для встроенных приложений) вы всегда можете изменить точку входа (или выйти) вашей программы, используя директивы предварительного процессора, такие как
#pragma startup [priority]
#pragma exit [priority]
Если приоритет является необязательным интегральным числом.
Запуск Pragma выполняет функцию перед тем, как основной (приоритетный) и выход прагмы выполняет функцию после основной функции. Если существует более одной директивы запуска, приоритет определяет, что будет выполняться первым.
Ответ 3
Нет необходимости перегружать. Да, есть 2 версии, но в то время можно использовать только один.
Ответ 4
Это одна из странных асимметрий и специальных правил языка C и С++.
По-моему, он существует только по историческим причинам, и нет реальной серьезной логики. Обратите внимание, что main
является особенным также по другим причинам (например, main
в С++ не может быть рекурсивным, и вы не можете взять его адрес, а на C99/С++ вы можете опустить окончательный оператор return
).
Обратите внимание, что даже в С++ это не перегрузка... либо программа имеет первую форму, либо имеет вторую форму; он не может иметь обоих.
Ответ 5
Что необычно для main
не в том, что его можно определить более чем одним способом, он может быть определен только одним из двух способов.
main
- пользовательская функция; реализация не объявляет прототип для него.
То же самое верно для foo
или bar
, но вы можете определять функции с этими именами так, как вам нравится.
Различие заключается в том, что main
вызывается реализацией (среда выполнения), а не только вашим собственным кодом. Реализация не ограничивается обычной семантикой вызова функции C, поэтому она может (и должна) иметь дело с несколькими вариантами, но не требует обработки бесконечно многих возможностей. Форма int main(int argc, char *argv[])
допускает аргументы командной строки, а int main(void)
в C или int main()
в С++ - это просто удобство для простых программ, которые не требуют обработки аргументов командной строки.
Что касается того, как компилятор справляется с этим, это зависит от реализации. Большинство систем, вероятно, имеют соглашения о вызовах, которые делают две формы эффективно совместимыми, и любые аргументы, переданные в main
, определенные без параметров, игнорируются. В противном случае компилятору или компоновщику не составит труда специально обработать main
. Если вам интересно, как это работает в вашей системе, вы можете посмотреть некоторые списки сборок.
И, как и многие другие на C и С++, детали в значительной степени являются результатом истории и произвольных решений, сделанных разработчиками языков и их предшественников.
Обратите внимание, что оба C и С++ допускают другие определения, определенные для реализации для main
, но редко есть веские основания для их использования. А для автономных реализаций (таких как встроенные системы без ОС) точка входа в программу определяется реализацией и необязательно даже называется main
.
Ответ 6
main
- это просто имя для начального адреса, решенного компоновщиком, где main
- имя по умолчанию. Все имена функций в программе - это начальные адреса, где начинается функция.
Аргументы функции выталкиваются/вывозятся из/из стека, поэтому, если для этой функции нет аргументов, нет аргументов, которые были нажаты/выведены из стека. Вот как основной может работать как с аргументами, так и без них.
Ответ 7
Ну, две разные подписи одной и той же функции main() появляются на картинке только тогда, когда вы этого хотите, я имею в виду, если ваша программа нуждается в данных перед любой фактической обработкой вашего кода, вы можете передать их с помощью -
int main(int argc, char * argv[])
{
//code
}
где переменная argc хранит количество передаваемых данных, а argv - массив указателей на char, который указывает на переданные значения из консоли.
В противном случае всегда хорошо идти с
int main()
{
//Code
}
Однако в любом случае в программе может быть один и только один основной(), поскольку потому, что единственная точка, где из программы запускает свое выполнение и, следовательно, не может быть больше одного.
(надеюсь, что он достоин)
Ответ 8
Ранее был задан аналогичный вопрос: Почему функция, не имеющая параметров (по сравнению с фактическим определением функции), компилируется?
Один из лучших ответов был:
В C func()
означает, что вы можете передать количество любых аргументов. если ты не нужно никаких аргументов, тогда вы должны объявить как func(void)
Итак, я предполагаю, что объявлен main
(если вы можете применить термин "объявленный" к main
). На самом деле вы можете написать что-то вроде этого:
int main(int only_one_argument) {
// code
}
и он все равно будет компилироваться и запускаться.
Ответ 9
Вам не нужно переопределять это. Потому что только один будет использоваться за один раз. Есть две разные версии основной функции