Подтвердить что ты не робот

Является ли int main() {} (без "void" ) действительным и переносимым в ISO C?

В стандарте C указаны две формы определения для main для хостинг:

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

и

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

Он может быть определен способами, которые эквивалентны вышеуказанному (для Например, вы можете изменить имена параметров, заменить int на typedef имя, определенное как int, или напишите char *argv[] как char **argv).

Он также может быть определен "каким-то другим способом, определенным реализацией" - это означает, что такие вещи, как int main(int argc, char *argv[], char *envp), действительны, если реализация документирует их.

Предложение "в некоторых других вариантах, определенных реализацией" не было стандарт 1989/1990; он был добавлен стандартом 1999 года (но более ранние стандартные разрешенные расширения, поэтому реализация может все еще допускают другие формы).

Мой вопрос таков: учитывая текущий (2011) стандарт ISO C, это определение формы

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

действительный и переносимый для всех размещенных внедрений?

(Обратите внимание, что я не обращаюсь ни к void main, ни к использованию int main() без круглых скобок в С++. Это как раз о различие между int main(void) и int main() в ISO C.)

4b9b3361

Ответ 1

Нет.

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

Ссылка: N1570 раздел 5.1.2.2.1. (Опубликованный стандарт ISO C 2011 года, который не является свободно доступный, имеет ту же формулировку, что и проект N1570.)

В пункте 1 говорится:

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

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

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

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

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

Использование слова "должно" вне ограничения означает, что любое которая нарушает его, имеет поведение undefined. Поэтому, если, например, я пишу:

double main(unsigned long ocelots) { return ocelots / 3.14159; }

для распечатки диагностики не требуется соответствующий компилятор, но он также не требуется компилировать программу или, если она компилируется он должен вести себя каким-либо образом.

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

int main(void) { }

предоставляет как декларацию (в данном случае прототип), так и определение. Объявление с помощью ключевого слова void указывает, что функция не имеет параметров. Определение указывает одно и то же.

Если я вместо этого напишу:

int main() { }

то я использую декларацию и определение старого стиля. (Такое декларации и определения устаревают, но они все еще часть определения языка, и все соответствующие компиляторы должны все еще поддерживают их.)

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

DR # 317 содержит постановление стандартного комитета C 2006, согласно которому определение с () не обеспечивает прототип, эквивалентный прототипу с (void) (благодаря hvd для поиска этой ссылки).

C позволяет main называть рекурсивно. Предположим, что я пишу:

int main(void) {
    if (0) {
        main(42);
    }
}

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

Или предположим, что я пишу:

int main() {
    if (0) {
        main(42);
    }
}

Если вызов main(42) был выполнен, он имел бы поведение undefined - но это не нарушает ограничение, и диагностика не требуется. Поскольку он защищен if (0), вызов никогда не происходит, и поведение undefined никогда не происходит. Если предположить, что int main(), то эта программа должна быть принята любым соответствующий компилятор. Но из-за этого он демонстрирует, что int main() не эквивалентен int main(void), и поэтому не распространяется на 5.1.2.2.1.

Вывод: Следуя формулировке стандарта, реализации разрешено документировать, что int main() { } является разрешенный. Если он не документирует его, ему все же разрешено принимать это без жалобы. Но соответствующий компилятор может также отклонить int main() { }, поскольку это не одна из форм, разрешенных стандарт, и поэтому его поведение undefined.

Но есть еще открытый вопрос: было ли это намерение авторов стандартного?

До публикации стандарта ANSI C 1989 года void ключевого слова не существует. Программы ANSI (K & R) C определяли бы main либо как

main()

или

int main()

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

Мое подозрение в том, что авторы стандарта C не намеревались чтобы сделать int main() недействительным. Но стандарт, как написано, не отражают это намерение; он, по крайней мере, разрешает соответствующий C-компилятор отклонить int main().

Практически говоря, вы можете почти наверняка с этим справиться. Каждый компилятор C, который я когда-либо пробовал, примет

int main() { return 0; }

без жалобы, с поведением, эквивалентным

int main(void) { return 0; }

Но по целому ряду причин:

  • Следуя как букве, так и намерению стандарта;
  • Избегайте использования устаревшей функции (будущий стандарт может удалить определения функций старого стиля);
  • Поддержание хороших привычек кодирования (разница между () и (void) важна для функций, отличных от main которые на самом деле вызываются другими функциями

Я рекомендую всегда писать int main(void), а не int main(). Он более четко указывает намерение, и вы можете быть на 100% уверены, что ваш компилятор примет его, а не 99,9%.

Ответ 2

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

6.5.3.4 Операторы sizeof и _Alignof

8 ПРИМЕР 3 В этом примере размер массива переменной длины вычисляется и возвращается из функции:

#include <stddef.h>

size_t fsize3(int n)
{
      char b[n+3];       // variable length array
      return sizeof b;   // execution time sizeof
}

int main()
{
      size_t size;
      size = fsize3(10); // fsize3 returns 13
      return 0;
}

6.7.6.3 Объявление функций (включая прототипы)

20 ПРИМЕР 4 Следующий прототип имеет измененный параметр.

void addscalar(int n, int m,
      double a[n][n*m+300], double x);

int main()
{
      double b[4][308];
      addscalar(4, 2, b, 2.17);
      return 0;
}

void addscalar(int n, int m,
      double a[n][n*m+300], double x)
{
      for (int i = 0; i < n; i++)
            for (int j = 0, k = n*m+300; j < k; j++)
                  // a is a pointer to a VLA with n*m+300 elements
                  a[i][j] += x;
}

Что касается фактического нормативного текста стандарта, я думаю, что слишком много читается в "эквивалентном". Должно быть довольно ясно, что

int main (int argc, char *argv[]) {
    (void) argc; (void) argv;
    return 0;
}

и что

int main (int x, char *y[]) {
    (void) argc; (void) argv;
    return 0;
}

неверно. Тем не менее стандарт явно указывает в нормативном тексте, что любые имена могут использоваться, что означает, что int main (int argc, char *argv[]) и int main (int x, char *y[]) считаются эквивалентными для целей 5.1.2.2.1. Строгий английский смысл слова "эквивалент" заключается не в том, как он предназначен для чтения.

Несколько более слабая интерпретация слова - это то, что предлагает Кейт Томпсон в своем ответе.

Справедливая даже более слабая интерпретация слова позволяет int main(): и int main(void), и int main() определять main как функцию, возвращающую int и не принимать параметры.

Ни стандарт, ни официальные DR в настоящее время не отвечают на вопрос о том, какая интерпретация предназначена, поэтому вопрос неопровержимый, но примеры настоятельно предполагают, что последняя интерпретация.

Ответ 3

Да.

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

эквивалентно

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

N1570 5.1.2.2.1/1

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

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

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

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

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

6.7.6.3/14

Список идентификаторов объявляет только идентификаторы параметров функции. Пустое список в объявлении функции, который является частью определения этой функции, указывает, что функция не имеет параметров. Пустой список в объявлении функции, который не является частью определение этой функции указывает, что никакой информации о количестве или типах параметры поставляются.

(акцент мой)

Как ясно указано в стандарте, определение int main() { /* ... */ } указывает, что funtion main не имеет параметров. И всем нам ясно, что это определение функции указывает, что возвращаемый тип функции main равен int. И так как 5.1.2.2.1 не требует, чтобы объявление main имело прототип, мы можем с уверенностью утверждать, что определение int main() { /* ... */ } удовлетворяет всем требованиям, предъявляемым стандартом (It [the main funtion] shall be defined with a return type of int and with no parameters, or [some other forms] .).

Тем не менее, вы никогда не должны использовать int main() {} в своем коде, потому что "использование деклараторов функций с пустыми скобками (не деклараторы типов параметров прототипа) является устаревшей функцией". (6.11.6), и поскольку эта форма определения не включает декларатор прототипа функции, компилятор не проверяет правильность числа и типов аргументов.

N1570 6.5.2.2/8

Никакие другие преобразования не выполняются неявно; в частности, количество и типы аргументы не сравниваются с параметрами в определении функции, что не включает декларатор прототипа функции.

(акцент мой)