Почему основная функция C может быть закодирована с параметрами или без них?

Просто интересно, почему

int main(void){} 

компиляции и ссылки

и так делает следующее:

 int main(int argc, char **argv){} 

Почему не нужно быть тем или иным?

gcc даже скомпилирует и свяжется с одним аргументом:

 int main(int argc){} 

но выдайте это предупреждение с помощью: -Wall:

 smallest_1.5.c:3:1: warning: 'main' takes only zero or two arguments [-Wmain] 

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

Ниже приведена точка зрения Linux.

main функция очень специфична в стандартном определении (для размещенных реализаций C11). Он также явно известен недавними компиляторами (как GCC & Clang / LLVM ….), которые имеют конкретный код для обработки main (и для того, чтобы дать вам это предупреждение). BTW, GCC (с помощью заголовков GNU libc через атрибуты функций ) также имеет специальный код для printf . И вы можете добавить свою собственную настройку в GCC, используя MELT для своих собственных атрибутов функции.

Для компоновщика main часто является обычным символом, но он вызывается из crt0 (скомпилируйте свой код с помощью gcc -v чтобы понять, что это на самом деле означает). BTW, ld (1) компоновщик (и файлы ELF , например исполняемые файлы или объектные файлы ) не имеет понятия типов или сигнатур функций и имеет дело только с именами (вот почему компиляторы C ++ выполняют некоторые манипуляции с именами).

И ABI и вызывающие соглашения так определены, что передача неиспользуемых аргументов функции (например, main или даже открытая (2) …) не наносит никакого вреда (несколько аргументов передаются в регистры). Для получения дополнительной информации прочтите x86-64 System V ABI .

См. Также ссылки в этом ответе .

Наконец, вы действительно должны практически определить свой main как int main(int argc, char**argv) и ничего больше, и вы, надеюсь, должны обрабатывать аргументы программы через них (по крайней мере --help & --version как это предусмотрено кодировкой GNU стандарты ). В Linux я ненавижу программы (и я проклинаю их программистов), не делая этого (так что обращайтесь --help & --version ).

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

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

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

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

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

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

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

Что касается параметров:

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

  1. Если параметры не объявлены: в качестве входных данных не ожидается никаких параметров.

  2. Если в main() есть параметры, они должны:

    • argc больше нуля.
    • argv[argc] – нулевой указатель.
    • argv[0] до argv[argc-1] являются указателями на строки, смысл которых будет определяться программой.
    • argv[0] будет строкой, содержащей имя программы или пустую строку, если она недоступна. Остальные элементы argv представляют аргументы, предоставленные программе. В случаях, когда имеется поддержка только символов одного регистра, содержимое этих строк будет передаваться программе в нижнем регистре.

В памяти:

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

На уровне машины:

они будут передаваться в регистрах, в зависимости от реализации.

Короткий ответ: если вы не используете параметры, вы можете объявить main без параметров двумя способами:

 int main(void) 

или же

 int main() 

Первым средством является функция без параметров. Вторая функция main – это функция с любым количеством параметров.

Поскольку вы не получаете доступ к параметрам, оба будут в порядке. Любой компилятор, имеющий «специальный» код для проверки параметров main , ошибочен. (Но: main должен вернуть значение.)

При этом он работает с бинарным форматом исполняемого файла и загрузчиком ОС. Компоновщику все равно (хорошо, что он немного заботится: ему нужно отметить точку входа), и единственной программой вызова является загрузчик.

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


Если у вас есть вопрос о конкретном OS / двоичном формате, вы можете уточнить.