Есть несколько заметок, которые нужно сделать.
Стандарт в основном говорит, что, скорее всего, главное: функция, не имеющая аргументов, или функция, принимающая два аргумента, или что-то еще!
См., Например, мой ответ на этот вопрос .
Но ваш вопрос указывает на другие факты.
Почему это работает? Ответ: потому что
стандарт так говорит!
Это не правильно. Это работает по другим причинам. Это работает из-за соглашений о вызовах.
Этими соглашениями могут быть: аргументы помещаются в стек, а вызывающая сторона отвечает за очистку стека. Из-за этого в реальном asm-коде вызываемый объект может полностью игнорировать то, что находится в стеке. Звонок выглядит как
push value1
push value2
call function
add esp, 8
(примеры Intel, просто чтобы оставаться в мейнстриме).
То, что функция делает с аргументами, помещенными в стек, совершенно неинтересно, все будет работать нормально! И это действительно так, даже если соглашение о вызовах отличается, например,
li $a0, value
li $a1, value
jal function
Если функция учитывает регистры $ a0 и $ a1 или нет, то ничего не меняется.
Таким образом, вызываемый объект может игнорировать без аргументов вреда, может быть, он считает, что они не существуют, или он может знать, что они существуют, но предпочитает игнорировать их (напротив, было бы проблематично, если вызываемый объект получает значения из стека или регистров, пока звонящий ничего не передал).
Вот почему все работает.
С точки зрения C, если мы находимся в системе, где код запуска вызывает main с двумя аргументами (int и char **) и ожидаем возвращаемое значение int, «правильным» прототипом будет
int main(int argc, char **argv) { }
Но давайте теперь предположим, что мы не используем эти аргументы.
Правильнее будет сказать int main(void)
или int main()
(все еще в той же системе, где реализация вызывает main с двумя аргументами и ожидает возвращаемое значение типа int, как сказано выше)?
Действительно, стандарт не говорит о том, что мы должны делать. Правильный «прототип», который говорит, что у нас есть два аргумента, все еще тот, который был показан ранее.
Но с логической точки зрения правильный способ сказать, что есть аргументы (мы их знаем), но мы не заинтересованы в них, это
int main() { /* ... */ }
В этом ответе Я показал, что происходит, если мы передаем аргументы функции, объявленной как int func()
, и что происходит, если мы передаем аргументы функции, объявленной как int func(void)
.
Во втором случае мы имеем ошибку, поскольку (void)
явно говорит, что функция не имеет аргументов.
С main
мы не можем получить ошибку, так как у нас нет реального прототипа, требующего аргументов для аргументов, но стоит отметить, что gcc -std=c99 -pedantic
не выдает предупреждения ни для int main()
, ни для int main(void)
, и это будет означает, что 1) gcc не соответствует C99, даже с флагом std
, или 2) оба способа соответствуют стандарту. Скорее это вариант 2.
Один явно соответствует стандарту (int main(void)
), другой действительно int main(int argc, char **argv)
, но без явного указания аргументов, поскольку они нас не интересуют.
int main(void)
работает, даже если существуют аргументы, из-за того, что я написал ранее. Но говорится, что главное не требует аргументов. Хотя во многих случаях, если мы можем написать int main(int argc, char **argv)
, тогда оно ложно, и вместо него должно быть предпочтительным int main()
.
Еще одна интересная вещь, на которую следует обратить внимание: если мы говорим, что main не возвращает значение (void main()
) в системе, где реализация ожидает возвращаемое значение, мы получаем предупреждение. Это связано с тем, что вызывающий объект ожидает, что он что-то с ним сделает, так что это «неопределенное поведение», если мы не возвращаем значение (что не означает помещения явного return
в случае main
, но объявления main
как возвращающий int).
Во многих кодах запуска, которые я видел, основной вызывается одним из следующих способов:
retval = main(_argc, _argv);
retval = main(_argc, _argv, environ);
retval = main(_argc, _argv, environ, apple); // apple specific stuff
Но могут существовать коды запуска, вызывающие main по-разному, например, retval = main()
;в этом случае, чтобы показать это, мы можем использовать int main(void)
, а с другой стороны, использование int main(int argc, char **argv)
скомпилирует, но приведет к аварийному завершению программы, если мы на самом деле будем использовать аргументы (так как полученные значения будут мусором).
Зависит ли эта платформа?
Способ вызова main зависит от платформы (зависит от реализации), что разрешено стандартами.«Предполагаемый» основной прототип является последовательностью, и, как уже было сказано, если мы знаем, что есть аргументы, переданные, но мы не будем их использовать, мы должны использовать int main()
в качестве краткой формы для более длинного int main(int argc, char **argv)
, а int main(void)
означает что-то другое: т.е. main не принимает аргументов (что неверно в системе, о которой мы думаем)