2015-03-22 5 views
22

Стандарт С определяет две формы определения для main для размещенных реализаций:Является ли int main() {} (без «void») действительным и переносимым в ISO C?

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() без скобок в C++. Это только о различии между int main(void) и int main() в ISO С)

ответ

23

Номер

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

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

Пункт 1 говорит:

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

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

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

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

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

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

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

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

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

int main(void) { } 

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

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

int main() { } 

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

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

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

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

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

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

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

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

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

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

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

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

main() 

или

int main() 

Основная цель стандарта ANSI, чтобы добавить новые функции (в том числе прототипов) без нарушение существующего кода pre-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%.

+3

Я хотел добавить что-то так плохо; которые выросли в дни менее компиляторов и обучаемых, написанных для них; но, действительно, вы полностью покрыли эту землю :) –

+1

Это действительно хороший ответ! Спасибо за то, что вы тоже поделились этим примером! –

+1

Следует отметить, что любые нарушения в этом случае относительно бессмысленны, если вы не используете переданные параметры и объявляете «нормальный» (то есть размер регистра). Любое несоответствие стека происходит только после возвращения 'main', после чего ваша программа уже завершена. – Blindy

7

Да.

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

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

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

N1570 5.1.2.2.1/1

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

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

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

(курсив мой)

+1

Вы должны добавить, что 5.1.2.2.1 содержит слова «или эквивалент». Это позволяет, например, «int main (int c, char ** v) {...», и если вы примете слова «или эквивалент» для применения к обоим случаям, предшествующим им, то у вас есть сильный аргумент (вместе с 6.7. 6.3: 14, который вы указали). –

+0

Этот ответ * определенно * отсутствует важные детали. Ответ Кит Томпсон уже показал конкретную разницу между 'int main()' и 'int main (void)'. – hvd

+2

@hvd На самом деле, я думаю, что этот ответ подчеркивает, что предложение «Если мы предположим, что int main() действителен, то эта программа должна быть принята любым соответствующим компилятором». Как используется в ответе Кейта Томпсона, это неправильно. Поскольку «пустой список в объявлении функции, который является частью определения этой функции, указывает, что функция не имеет параметров». –

12

сильный признак того, что 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 и не принимает никаких параметров.

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

+0

Примеры являются отличным аргументом и, как правило, подтверждают, что * намерение * должно было разрешать 'int main() {}' (точнее, требовать соответствия реализаций для принятия 'int main() {}'). Я до сих пор считаю, что нормативная формулировка стандарта делает 'int main() {}' не совсем соответствующим, но это зависит от интерпретации «эквивалента». –

+0

Что недействительно в вашем последнем примере, кроме ссылок на неопределенные значения? – cat

+1

@cat Именно это '(void) argc; (void) argv; 'недопустимо, потому что' argc' и 'argv' не были объявлены. В этом есть смысл: в чрезмерно строгом смысле 'int main (int x, char * y [])' не эквивалентен 'int main (int argc, char * argv [])'. – hvd