Функция main в C и C++: сигнатуры, argc/argv и что возвращать

Функция main в C и C++: сигнатуры, argc/argv и что возвращать Полезное

main — это функция, с которой операционная система начинает выполнение программы на C или C++ (точка входа). Стандарт определяет для нее ровно две переносимые формы: int main(void) без аргументов и int main(int argc, char *argv[]) с аргументами командной строки. Разберем обе сигнатуры, что означает возвращаемое значение, как устроены argc и argv, где C и C++ ведут себя по-разному. Код ниже собран компилятором и запущен, вывод приведен как есть.

Минимальный рабочий пример

Самая короткая программа на C++, которую можно скомпилировать и запустить, выглядит так:

#include <iostream>

int main() {
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

Вывод в консоль:

Hello, World!

Разберем по строкам. #include <iostream> подключает заголовок стандартной библиотеки для потоков ввода-вывода. int перед main — тип возвращаемого значения: функция вернет операционной системе целое число. std::cout — стандартный поток вывода, std:: указывает на пространство имен стандартной библиотеки. return 0 сообщает системе, что программа завершилась без ошибок.

Тело функции — это то, что между фигурными скобками { и }. До main, в начале файла, обычно стоят только директивы #include и объявления.

Допустимые сигнатуры

Стандарт C и C++ гарантирует переносимость двух форм: без параметров и с параметрами командной строки. Остальные записи либо специфичны для платформы, либо просто ошибочны. Сведем это в таблицу.

Сигнатура C C++ Смысл
int main(void) да да без аргументов; в C (void) явно говорит «параметров нет»
int main() зависит от версии да в C++ то же, что (void); в C до C23 означало «список параметров не задан», с C23 — тоже «нет параметров»
int main(int argc, char *argv[]) да да с аргументами командной строки
int main(int argc, char **argv) да да та же форма, другая запись массива указателей
void main() нестандартно нестандартно не по стандарту, переносимости нет

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

Что возвращает main и зачем

Тип int перед main — это код завершения, который получает операционная система или вызвавший процесс. По соглашению 0 означает успешное завершение, ненулевое значение — ошибку. Чтобы не привязываться к конкретным числам, в <stdlib.h> (в C++ — <cstdlib>) определены макросы EXIT_SUCCESS и EXIT_FAILURE.

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    printf("Gotovo\n");
    return EXIT_SUCCESS;
}

Вывод и код возврата:

Gotovo

Программа завершилась с кодом 0. Проверить его в оболочке можно командой echo $? сразу после запуска.

Здесь есть важное различие между языками. В C++ return в main можно не писать: если управление дошло до закрывающей скобки, компилятор сам подставит return 0. В C то же поведение гарантировано стандартом начиная с C99. Пример на C++ без return:

#include <iostream>

int main() {
    std::cout << "Bez return" << std::endl;
}

Вывод:

Bez return

Код возврата все равно 0, хотя return мы не писали. Это работает только для main: любая другая функция с типом int обязана возвращать значение явно.

argc и argv: аргументы командной строки

Вторая форма main принимает то, что пользователь передал программе при запуске. argc (argument count) — число аргументов, argv (argument vector) — массив строк с самими аргументами. Важная деталь: argv[0] — это имя, под которым запустили программу, поэтому argc всегда не меньше 1, а собственно параметры начинаются с argv[1].

#include <iostream>

int main(int argc, char *argv[]) {
    std::cout << "Vsego argumentov: " << argc << std::endl;
    for (int i = 0; i < argc; ++i) {
        std::cout << "argv[" << i << "] = " << argv[i] << std::endl;
    }
    return 0;
}

Запуск с аргументами file.txt -v 42 дает такой вывод:

Vsego argumentov: 4
argv[0] = ./args
argv[1] = file.txt
argv[2] = -v
argv[3] = 42

argc равен 4: имя программы плюс три переданных слова. Элемент argv[argc] по стандарту равен нулевому указателю, так что массив можно обходить и до NULL, но опираться удобнее на argc.

Разбор аргументов: превращаем строку в число

argv — это всегда строки, даже если пользователь ввел цифры. Чтобы получить число, строку нужно разобрать: в C для этого есть strtol. Заодно покажем, как проверять, что аргументов передали столько, сколько нужно, и завершаться с ошибкой, если нет.

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Ozhidayu 2 chisla, poluchil %d\n", argc - 1);
        return EXIT_FAILURE;
    }
    long a = strtol(argv[1], NULL, 10);
    long b = strtol(argv[2], NULL, 10);
    printf("%ld + %ld = %ld\n", a, b, a + b);
    return EXIT_SUCCESS;
}

При правильном вызове с двумя числами:

2 + 3 = 5

Код возврата 0. А если чисел не хватает, программа пишет в поток ошибок и завершается с ненулевым кодом:

Ozhidayu 2 chisla, poluchil 1

Код возврата 1 (EXIT_FAILURE). Такой контракт «нет аргументов — ошибка» и позволяет другим программам и скриптам понимать, отработала ли ваша утилита.

Типичная ошибка: void main

Иногда в старых учебниках встречается запись void main(). Она не соответствует стандарту ни C, ни C++, и современный компилятор на нее ругается. Неверный вариант:

#include <stdio.h>

void main(void) {
    printf("void main\n");
}

Фактический результат сборки (Apple Clang 21):

error: 'main' must return 'int'

Исправление — вернуть тип int и код завершения:

#include <stdio.h>

int main(void) {
    printf("Vernyj main\n");
    return 0;
}

Строгость реакции зависит от компилятора: часть тулчейнов выдает предупреждение, а не ошибку, и молча собирает такой код. Именно поэтому новичок легко пропускает проблему. Правило простое: main всегда возвращает int.

В чем C и C++ расходятся

Формы сигнатур в двух языках совпадают, но есть нюансы, которые стоит держать в голове.

  • int main() в C++ строго означает «нет параметров». В C до стандарта C23 пустые скобки означали «список параметров не указан», а не «параметров нет»; чтобы явно сказать «без аргументов», в C пишут int main(void).
  • Отсутствие return в main в C++ было легально всегда, в C — начиная с C99.
  • Тип возврата и общий смысл argc/argv в обоих языках одинаковы, поэтому базовые примеры переносятся между C и C++ почти без изменений.

Выводы

  • main — точка входа программы на C и C++; переносимых сигнатур две: int main(void) и int main(int argc, char *argv[]).
  • Возвращаемое значение — код завершения для ОС: 0 (или EXIT_SUCCESS) — успех, ненулевое (EXIT_FAILURE) — ошибка.
  • В C++ return в main можно опустить, компилятор подставит return 0; в C это гарантировано с C99.
  • argc — число аргументов (минимум 1), argv[0] — имя программы, реальные параметры идут с argv[1]; строки из argv разбираются функциями вроде strtol.
  • void main — не по стандарту; всегда используйте тип int.

Где применяется / связь с практикой

Освойте тему на практике

Правильно оформленная main и разбор argc/argv — это основа любых консольных утилит: от учебных до системных инструментов, которые принимают флаги и пути к файлам. На них же строятся коды возврата, по которым автотесты и скрипты сборки понимают, прошла ли программа. Разобраться с этим на практике, писать код и получать разбор от преподавателя удобнее в структурированном курсе — например, на курсе «C++ Basic» в Otus. Посмотреть формат занятий и темы можно на открытых уроках Otus — они бесплатные и проходят регулярно.

FAQ

Может ли main вызывать сама себя или другая функция вызвать main?
В C++ вызывать main из кода программы запрещено стандартом. В C это формально не запрещено, но так почти никогда не делают: это точка входа, а не обычная функция для рекурсии.

Чем main отличается в С++ от функций, у которых нельзя опустить return?
Право не писать return — особый случай именно для main. Любая другая функция с типом int при отсутствии return дает неопределенное поведение, поэтому значение нужно возвращать явно.

Почему argv имеет тип char *argv[], а не просто строку?
Это массив указателей на строки: каждый аргумент — отдельная C-строка, а argv хранит адреса их начал. Запись char *argv[] и char **argv означают одно и то же.

OTUS Журнал
Скидка 5% 12-19 октября на курсы (popup)
Бесплатные открытые уроки (поп-ап)