Главная » Статьи » Программирование [ Добавить статью ]

Ересь в виде void main(), использование функции main

Функция main() возвращает тип int. Если вы уже это знаете, то и не читайте.

Если вы присвоите функции main() тип возврата, отличный от int, то в компиляторах, предшествующих компиляторам С99, вы получите неопределенное поведение своей программы. В компиляторах С99 вы получите неспецифицированное поведение, если так говорит реализация версии, или неопределенное поведение — если она этого не делает. Доверяете ли вы своей программе в этом отношении?

Многие просто не верят мне, когда я говорю им это (точно так же, как не верил я, когда впервые узнал об этом). Частично это связано с тем, что несколько широко известных учебников по С и, по крайней мере, по одной авторитетной программе-компилятору используют void main() с тревожной регулярностью. Ниже приведена формулировка стандарта С99 (который фактически чуть более снисходителен, чем стандарт С89, который вам, возможно, более знаком):

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

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

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

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

либо некоторым другим определенным реализацией способом".

5.1.2.2.3. Завершение программы. Если возвращаемый главной функцией тип является типом, совместимым с int, то возврат в главную функцию эквивалентен вызову функции выхода со значением, возвращенным главной функцией в качестве ее аргумента; при достижении скобки }, которая завершает главную функцию, возвращается значение 0. Если возвращаемый тип несовместим с типом int, состояние завершения, возвращаемое в хост-среду, является неспецифицированным".

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

Давайте посмотрим на это несколько под иным углом. Рассмотрим четвертый аргумент функции сортировки qsort. Он специфицирован как указатель на некую функцию сравнения, принимающую в качестве аргументов две константы типа const void * и возвращающую тип int. Функция qsort вызывает эту функцию сравнения и использует возвращенное ею значение для установления взаимоотношений между двумя объектами в массиве, который подлежит сортировке. Так вот, что случится, если вы напишете функцию сравнения, подобную этой?

void CompInts (const void *pl,  const void *p2)
{
    const int  *nl  = pi;
    const int  *n2  = p2;
    int diff;
    if(*nl > *n2)
        diff = 1;
    else if(*nl == *n2)
        diff = 0;
    else
        diff = -1;
}

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

Хорошо, вернемся к функции main (). Здесь точно такая же ситуация. Вы не ответственны за определение интерфейса main(). Для этого есть вызывающая функция. Кто вызывает main()? Это не вы (хотя фактически вы можете, если захотите, вызвать main(), точно так же как вы можете при желании вызвать свою функцию сравнения из qsort). Но первичным "заказчиком" функции main() является код запуска. Как же код запуска определит, успешно ли завершилась программа, если вы не сообщите ему об этом? Этот вызов "сидит" где-то глубоко во внутренностях системы (здесь я немного упростил его):

int returnstatus;
returnstatus = main  (argc,  argv);


Если вы "опустошите" функцию main (), появится несколько интересных возможностей:

  • Программа может работать в точности так, как вы ожидаете.
  • returnstatus может попасть в ловушку и вызвать аварийный отказ программы (либо всего компьютера).
  • Код запуска может отправить поддельный код возврата операционной системе, которая затем решит перемотать назад транзакции базы данных, поскольку программа не возвратила ожидаемого значения.
  • А это хуже всего — код запуска может снаружи достигнуть вашего носа и начать извлекать из него демонов. (Демон (Demon) — процедура, запускаемая автоматически при выполнении некоторых условий и характеризуемая непредсказуемостью поведения. — Прим. науч. ред.).

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

  • 0
  • EXITSDCCESS
  • EXIT_FAILURE

Два последние определены в <stdlib.h>, и их фактические значения варьируются в зависимости от конкретной системы. (Другими словами, не следует отыскивать эти значения в библиотеке <stdlib.h> и переносить их в свою программу.)

Если вы вернули 0, код запуска сообщит операционной системе или другой хост-среде, что ваша программа выполнилась успешно, переведя 0 при необходимости в некоторое другое значение.

Количество аргументов функции main ()

Фактически функцию можно определить путем определения реализации. Следовательно, такое определение main, как

Code
int main  (int argc)

ИЛИ

int main  (int argc,  char **argv,  char **env)

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

Ниже приведены переносимые определения функции main ():

  • int main (void)
  • int main (int argc, char **argv)
  • int main (int argc, char **argv [ ] )
  • Любое определение, в точности эквивалентное любому из трех предыдущих

Таким образом, вы можете использовать различные имена переменных в качестве argc и argv, а также можете использовать FOO, если определению предшествует typedef int FOO, и т.д. Но чтобы возвращаемое значение было переносимым, из main вы должны возвращать тип int, а также задавать либо ни одного аргумента, либо два специфицированных стандартом аргумента. Если есть необходимость получить доступ к среде переносимым способом, можете, конечно, использовать функцию getenv ().

© Ричард Хэзфилд, "Искусство программирования на C", 2001 год.



Добавлено: 23.07.2011 | Просмотров: 10483 | Рейтинг: 5.0/1 |
Теги: Void, Main, C++


Комментарии (0)
Имя *:
Email *:
Код *: