Как работает метод 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

Вам не нужно переопределять это. Потому что только один будет использоваться за один раз. Есть две разные версии основной функции