Содержание

Функции

Есть два способа определения функции. Первый способ:

класс-памяти тип-результата идентификатор ( список-типов-параметров )
{
    список-деклараций

    список-команд
}

Класс-памяти может быть либо extern, либо static. Если класс памяти пропущен, то подразумевается extern.

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

Идентификатор является именем функции.

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

тип id1, id2

в списке параметров не допускается.

Функция имеет переменное число аргументов, если список-типов-параметров заканчивается ,....

Любой параметр объявленный как «массив типа» изменяется на «указатель на тип». Любой параметр декларированный как «функция» изменяется на «указатель на функцию».

Следующие примеры демонстрируют несколько определений функции:

int F( void )

Функция F не имеет параметров и возвращает int.

void G( int x )

Функция G имеет один параметр, объект целого типа именуемый x, и не возвращает никакого значения.

void * H( long int len, long int wid )

Функция H имеет два параметра, объекты длинного целого типа len и wid, и возвращает указатель, который указывает на объект неопределённого типа.

void I( char * format, ... )

Функция I имеет один известный параметр, объект с именем format, который указывает символ (стринг). Функция также принимает переменное число параметров после format. Функция не возвращает никакого результата.

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

Второй способ определения функции:

класс-памяти тип-результата идентификатор ( список-идентификаторов )
список-деклараций
{
    список-деклараций

    список-команд
}

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

В декларациях идентификаторов параметров допускается только один из спецификаторов класса памяти — register.

Прототип функции создаётся из определения, после расширения по умолчанию типа каждого из аргументов. Над всеми аргументами функции декларированной таким способом будет производится расширение типа по умолчанию. Результирующие типы должны совпадать с типами декларированных параметров после расширения. В противном случае поведение не определено.

Заметьте, что функции, декларированной таким способом, невозможно передать объект типа float. Аргумент типа float будет автоматически расширен до double, и параметр также будет расширен до double (подразумевается, что он декларирован как float). По тем же причинам невозможно передать нерасширенными объекты типа char или short int.

В соответствии со стандартом ANSI этот способ определения функции — устаревший и не должен использоваться. Он существует только по историческим причинам, в частности, для совместимости со старыми компиляторами C. Использование первого способа определения функции зачастую позволяет компилятору генерировать лучший код.

Следующие примеры — те же, что и для первого способа с соответствующими изменениями:

int F()

Функция F не имеет параметров и возвращает целое.

void G( x )

Функция G имеет один параметр, целое x, и не возвращает никого значения. Этот пример можно написать и так:

void G( x )
    int x;

x явно декларирован как целое.

void * H( len, wid )
    long int len;
    long int wid;

Функция H имеет два параметра, целые len и wid, и возвращает указатель, который указывает на объект неопределённого типа. Любой вызов этой функции должен гарантировать, что передаются длинные целые, либо передавая объекты этого типа, либо явно преобразуя их в этот тип.

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

Тело функции

Тело функции следует за её декларацией и открывающей фигурной скобкой. Оно состоит из двух частей, обе из которых необязательны.

Первая часть является списком деклараций всех объектов, которые необходимы в функции. Эти объекты могут иметь любой тип и класс памяти. Объекты с классом памяти register или auto имеют автоматическую продолжительность хранения, что означает, что они создаются при вызове функции и уничтожаются при завершении функции. (Значение объекта не сохраняется между вызовами функции.) Объекты с классом памяти extern и static имеют статическую продолжительность хранения, что означает, что они создаются однажды, до какого-либо вызова функции, и уничтожаются только тогда, когда заканчивается программа. Любое значение помещённое в такой объект, будет сохраняться и после возврата из функции, так, что при следующем вызове оно будет тем же (если, конечно, над объектом не будет произведено какого-либо действия, например, при помощи указателя, содержащего его адрес).

Если не выполнится явная команда return, то функция не вернёт управления, пока не встретится закрывающая фигурная скобка в конце её определения. Возврат будет таким же, как и при использовании команды return без выражения. Если при этом функция определена как возвращающая некое значение, а вызывающая команда попытается использовать его, то поведение не определено. Используемое значение будет случайным.

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

Прототипы функций

Прототип функции похож на определение без тела. Непосредственно после правой круглой скобки в декларации функции ставится точка с запятой. Прототип описывает имя функции, типы параметров, которые она ожидает (имена необязательны) и тип возвращаемого значения. Эту информацию компилятор C может использовать для проверки и правильного преобразования типов аргументов во время вызова, а также для правильной обработки возвращаемого значения.

Если во время вызова функции не найден её прототип, то все аргументы подвергаются расширению по умолчанию, и подразумевается, что тип возвращаемого значения — int. Если фактическое определение функции не содержит параметров, совпадающих с расширенными типами, то поведение не определено. Если возвращаемое значение — не int, и оно необходимо, то поведение не определено.

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

Все библиотечные функции имеют прототипы в одном или нескольких заголовочных файлах. Они должны включаться всегда, когда используется описанная в них функция. Подробности см. «Watcom C Library Reference».

Переменные списки аргументов

Если прототип (и определение) функции содержат список параметров, который оканчивается ,... , то функция имеет переменный список аргументов или переменный список параметров, что значит, что число аргументов функции может изменяться. (В качестве примера можно привести библиотечную функцию printf.) До переменной части должен быть указан хотя бы один аргумент. Этот аргумент обычно описывает неким образом, сколько ещё аргументов следует ожидать. Он может быть просто счётчиком, а может включать в себя (как в printf) описание числа аргументов и их типы.

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

Так как параметры представленные ,... не имеют имён, то они требуют специального обращения. Язык C предоставляет специальный тип и три макро для обработки переменных списков аргументов. Для их использования нужно включить заголовочный файл <stdarg.h>.

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

Макро va_start имеет следующую форму:
void va_start( va_list пар_инф
    ,
    посл_пар
    );

Макро заполняет объект пар_инф информацией описывающей переменный список. Аргумент посл_пар является именем (идентификатором) последнего параметра перед ,... и должен быть объявлен без спецификатора класса памяти register.

Макро va_start должно быть выполнено до любой обработки переменной части списка параметров.

va_start может исполняться более одного раза, но только после того, как исполнится va_end.

Макро va_arg имеет следующую форму

тип va_arg( va_list
    пар_инф
    ,
    тип
    );

пар_инф является тем же объектом, что и при вызове va_start. Тип — тип ожидаемого аргумента. Ожидаемыми типами могут быть только те, которые получаются в результате расширения аргументов по умолчанию (int, long int и их беззнаковые варианты, double и long double), и те, которые не подвергаются расширению (указатели, структуры и объединения). Тип должен определяться программой. Макро va_arg расширяется в выражение, которое имеет тип и значение следующего параметра в переменном списке.

В случае printf ожидаемые типы параметров определяются из «спецификаторов преобразований», таких как %s, %d и т.д.

Первый вызов макро va_arg (после исполнения va_start) возвращает значение параметра следующего после посл_пар (указанного в va_start). Каждый последующий вызов va_arg возвращает очередной параметр в списке. При каждом вызове значение пар_инф изменяется (неким способом, зависящим то реализации) для отображения процесса обработки списка параметров.

Если тип следующего параметра не совпадает с типом, или параметр не был указан, то поведение не определено.

Макро va_end имеет следующую форму

void va_end( va_list пар_инф
    );

пар_инф — тот же объект, что и соответствующий в вызове va_start. Функция va_end заканчивает обработку переменного списка аргументов, и это нужно сделать до возврата из функции. Если va_end не вызвана до возврата из функции, то поведение не определено.

Если va_end вызвана без соответствующего вызова va_start, то поведение не определено.

После вызова va_end и до возврата из функции можно опять вызвать va_start и заново обработать переменный список. При этом до возврата из функции необходимо будет снова вызвать va_end.

Следующая функция принимает в качестве параметров счётчик и произвольное количество чисел с плавающей точкой и возвращает среднее:

#include <stdarg.h>

extern double Average( int count, ... )
/*************************************/
{
    double  sum = 0;
    int     i;
    va_list parminfo;

    if( count == 0 ) {
        return( 0.0 );
    }
    va_start( parminfo, count );
    for( i = 0; i < count; i++ ) {
        sum += va_arg( parminfo, double );
    }
    va_end( parminfo );
    return( sum / count );
}

Параметры функции main

Функция main имеет специальное назначение в C. Это функция, которая получает управление после старта программы. Функция main имеет следующее определение:

extern int main( int argc, char * argv[] )
/****************************************/
{
    команды
}

Объекты argc и argv имеют следующие свойства:

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

В Watcom C/16 и C/32 каждая лексема в командной строке, не в кавычках, отделённая пробелом, становится элементом argv. Стринги в кавычках сохраняются как один элемент без кавычек.

Например, командная строка

pgm 2+ 1 tokens "one token"

сделает argc равным 5, а элементами argv будут стринги "pgm", "2+", "1", "tokens" и "one token".

Функция main может быть декларирована без параметров:

extern int main( void )
/*********************/
{
    команды
}

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

Для прекращения программы в любой точке можно использовать библиотечную функцию exit. Значение её аргумента возвращается, как если бы main имела возвращаемое значение.