Функция это независимый набор объявлений и операторов, который обычно разрабатывается для выполнения конкретной задачи. Программы на языке С имеют по крайней мере одну функцию, main, и могут содержать и другие функции. В данной Главе рассматривается, как можно определять, объявлять и вызывать функции в языке программирования С.
В определении функции задается ее имя, тип и число ее формальных параметров и приводятся объявления и операторы, которые задают ее действие. Эти объявления и операторы называются "телом функции". Кроме того, определение функции задает ее тип возвращаемого значения и ее класс хранения. Если возвращаемый тип и класс хранения не заданы явно, то по умолчанию они устанавливаются соответственно int и extern.
Прототип функции (или ее объявление) задает имя, возвращаемый тип и класс хранения для функции, полное определение которой содержится в каком-либо другом месте программы. Он также может содержать объявления, задающие тип и число формальных параметров функции. Объявления формальных параметров могут задавать имена для формальных параметров, хотя сфера действия этих имен заканчивается в конце объявления. Для формального параметра может быть задан класс хранения register.
Пример
В данном примере дается четкое и ясное объявление прототипа и определение форматов. Показано, что прототип функции имеет тот же вид, что и определение функции, за тем исключением, что прототип заканчивается точкой с запятой.
Компилятор использует прототип или объявление для сравнения типов действительных аргументов в вызове функции с формальными параметрами функции, даже если явное определение функции отсутствует. Явное задание прототипов и объявлений для функций, возвращающих значение int необязательно. Однако, для обеспечения корректности выполнения нужно обязательно объявить или определить функции с другими возвращаемыми типами до их вызова. (Объявления прототипов функций будут рассмотрены позднее в Разделе "Определения функций (Прототипы)" данной Главы и в Главе "Объявления".)
Если не задан прототип или объявление функции, то ее прототип создается по умолчанию на основании той информации, которая имеется при первой же ссылке на функцию. Это может быть вызов или определение функции. Однако, такой прототип по умолчанию может неадекватно представлять определение или вызов функции.
При вызове функции управление выполнением программы передается на вызванную функцию. Действительные аргументы, если они есть, передают свои значения вызванной функции. Выполнение оператора return в вызванной функции возвращает управление и возможное возвращаемое значение на вызов функции.
Примечание
Строго рекомендуется использовать прототипы функций. Иногда они могут дать единственную возможность для компилятора проверить корректность передачи аргументов. Прототипы позволяют компилятору либо диагностировать, либо корректно обработать несоответствие аргументов, которые иначе не могут быть обработаны до выполнения программы.
/** Объявление и определение прототипа функции **/
double new_style(int a, double *x); /* прототип функции */
double alt_style(int, double *); /* альтернативная форма
прототипа */
double old_style(); /* устаревшая форма объявления
функции */
.
.
.
double new_style(int a, double *real); /* стиль */
{ /* определения */
return (*real + a); /* функции прототипа */
}
double alt_style(int, double *); /* старая форма */
double *real; /* определения функции */
int a;
{
return (*real + a);
}
Синтаксис: [спецификатор-класса]
[спецификатор-типа]декларатор
([список-формальных-параметров])
тело-функции
В "определении функции" задается ее имя, формальные
параметры и тело функции. Кроме того, может задаваться
возвращаемый функцией тип и класс хранения.
Необязательный "спецификатор-класса" задает класс хранения функции, который может быть static или extern.
Необязательный "спецификатор-типа" и обязательный "декларатор" вместе задают имя и возвращаемый функцией тип. Декларатор является комбинацией идентификатора, который задает имя функции, и скобок, которые следуют за именем.
"Список-формальных-параметров" это последовательность объявлений формальных параметров, разделенных запятыми. Объявление каждого формального параметра в списке должно иметь следующий вид:
[register] спецификатор-типа [декларатор]
[,...]
Список формальных параметров содержит объявления параметров
функции. Если функции не будут передаваться аргументы, то список
должен содержать ключевое слово void. Допустимо использование
пустых скобок (()), но в этом случае нет никакой информации о
том, будут ли передаваться аргументы. Список формальных
параметров может быть полным или частичным. Вторая строка в
приведенном выше синтаксисе (запятая, за которой
следует многоточие) является частичным списком параметров и
показывает, что в функцию могут быть переданы дополнительные
параметры, но никакой информации о них не задается. Для таких
аргументов не производится проверка их типа. По крайней мере один
формальный параметр должен предшествовать многоточию и оно должно
стоять последним элементом в списке параметров. Без указания
многоточия поведение функции будет непредсказуемым при передаче
ей дополнительных параметров, не указанных в списке формальных
параметров. Если задан прототип функции, то производится
автоматическая проверка и преобразование типов. Если относительно
формальных параметров нет никакой информации, то над
необъявленными аргументами проводится обычное арифметическое
преобразование.
"Спецификатор-типа" можно не задавать, только если задан класс хранения register для значения типа int.
"Тело-функции" это составной оператор, содержащий локальные объявления переменных, ссылки на внешне объявленные элементы и операторы.
Примечание
Старая форма объявления и определения функции все еще будет работать, но считается устаревшей. В новых программах рекомендуется использование прототипа. Старая форма определения функции имеет следующий синтаксис:
[спецификатор-класса][спецификатор-типа]декларатор
([список-идентификаторов])[объявления-параметров]
тело-функции
"Список-идентификаторов" это необязательный список
идентификаторов, которые функция может использовать в качестве
имен своих формальных параметров. Аргументы
"объявления-параметров" устанавливают тип формальных параметров.
В Разделах 7.2.1 - 7.2.4 содержится детальное описание отдельных частей определения функции.
С помощью спецификатора класса хранения в определении функции можно задать класс хранения для функции extern или static. Если определение функции не содержит спецификатора класса хранения, то по умолчанию устанавливается класс хранения extern. Можно явно задать спецификатор класса хранения extern в определении функции, хотя это и не обязательно.
Функция с классом хранения static доступна только в том исходном файле, где она появляется. Все другие функции, явно или неявно определенные с классом хранения extern, доступны из всех исходных файлов, составляющих программу.
Если желательно задать класс хранения static, то нужно это объявить при первом появлении объявления функции, если оно есть, и в определении функции.
Синтаксис: [спецификатор-класса]
[спецификатор-типа]декларатор
([список-формальных-параметров])
Возвращаемый функцией тип устанавливает размер и тип
значения, возвращаемого функцией, и полностью соответствует
"спецификатору-типа" в приведенном выше синтаксисе. Спецификатор
типа может задать любой фундаментальный, структурный или
тип об'единения. Если спецификатор типа не задан, то по умолчанию
предполагается, что возвращаемый тип int.
Декларатор это идентификатор функции, который может быть модифицирован на тип указателя. Следующие за идентификатором скобки определяют элемент в качестве функции. Функции не могут возвращать массивы или функции, но они могут возвращать указатели на любые типы, включая массивы и функции.
Заданный в определении функции возвращаемый тип должен соответствовать возвращаемому типу в объявлении функции, которое находится в каком-либо другом месте программы. Нет необходимости объявлять функции с возвращаемым типом int до их вызова, хотя наличие прототипов рекомендуется для обеспечения проверки корректности аргументов. Однако, функции с другими возвращаемыми типами должны быть определены или объявлены до их вызова.
Возвращаемый тип функции используется только тогда, когда функция возвращает какое-либо значение. Функция возвращает значение при выполнении в ней оператора return, содержащего выражение. Это выражение вычисляется, при необходимости преобразуется к типу возвращаемого значения и эта величина передается на место вызова функции. Если оператор return не выполняется или если оператор return не содержит выражения, в этом случае возвращаемое значение не определено. Если при вызове функции предполагается получение от нее возвращаемого значения, а оно не определено, то и поведение программы в целом также не определено.
Пример 1
В данном примере функция add по умолчанию возвращает тип int. Функция имеет класс хранения static, это означает, что ее могут вызывать только функции этого исходного файла. Объявленный в заголовке список формальных параметров включает в себя int значение x, для которого запрошен класс хранения register, и int значение y. Определение второй функции subtract дано в старой форме. По умолчанию ее возвращаемый тип int. Формальные параметры объявляются между заголовком и открывающей скобкой.
/* определение прототипа */
static add (register x, int y)
{
return (x+y);
}
/* определение старой формы */
sutract (x , y)
int x, y;
{
return (x-y);
}
Пример 2
В данном примере определяется тип STUDENT с объявлением typedef и определяется функция sortstu с возвращаемым типом STUDENT. Функция выбирает и возвращает один из своих двух структурных аргументов. Данное определение прототипа имеет формальные параметры, объявленные в заголовке. При вызове функции компилятор будет проверять соответствие заданных аргументов типу STUDENT. Можно увеличить эффективность, если передавать указатель на структуру, а не саму структуру.
typedef struct {
char name[20];
int id;
long class;
} STRUCT;
/* возвращаемый тип STUDENT */
STUDENT sortsu (STUDENT a, STUDENT b)
{
return ( (a.id < b.id) ? a : b);
}
Пример 3
В данном примере использована старая форма определения функции, возвращающей указатель на массив символов. Функция принимает в качестве аргументов два массива символов (строки) и возвращает указатель на наиболее короткую из двух строк. Пойнтер на массив указывает на тип элементов массива, следовательно, возвращаемый функцией тип это указатель на char.
/* возвращаемый тип это указатель на char */
char *smallstr(s1, s2)
char s1[], s2[];
{
int i;
i=0;
while ( s1[i] != '\0' && s2[i] != '\0' )
i++;
if ( s1[i] == '\0' )
return (s1);
else
return (s2);
}
"Формальные параметры" это переменные, которые принимают передаваемые при вызове функции значения. В определении прототипа функции скобки, которые следуют за именем функции, содержат полные объявления формальных параметров функции.
Примечание
В старой форме определения функции формальные параметры определялись после закрывающей скобки, непосредственно перед началом составного оператора, составляющего тело функции. В этой форме список идентификаторов в скобках задавал имена всех формальных параметров и порядок, в котором они принимали значения из вызова функции. Список идентификаторов мог содержать несколько идентификаторов, разделенных запятыми, или ни одного идентификатора. Список должен был быть заключен в скобки даже тогда, когда он был пуст. Такая форма записи считается устаревшей и ей не следует пользоваться при написании новых программ.
Если в списке формальных параметров есть по крайней мере один формальный параметр, то список может заканчиваться запятой с многоточием (,...). Эта конструкция указывает на переменное число аргументов функции. Однако, ожидается, что в вызове функции будет по крайней мере столько же аргументов, сколько формальных параметров присутствует до последней запятой. В старой форме определения запятая с многоточием может следовать за последним идентификатором в списке идентификаторов.
Если в функцию не будут передаваться аргументы, то список формальных параметров заменяется ключевым словом void. Такое использование void отличается от его использования в качестве спецификатора типа.
В объявлениях формальных параметров задаются типы, размеры и идентификаторы для значений, хранимых в формальных параметрах. В старой форме определения функции такие объявления имеют вид объявлений других переменных (см. Главу "Объявления"). Однако, в определении прототипа функции каждому идентификатору в списке формальных параметров должен предшествовать его спецификатор типа. Например, в следующем определении функции (старого вида) old, double x, y, z; могут быть объявлены простым отделением запятой идентификаторов:
void old(x, y, z)
double z, y;
double x;
{
;
}
void new(double x, double y, double z)
{
;
}
Функция с именем new определена в формате прототипа со
списком формальных параметров в скобках. При таком способе записи
спецификатор типа double должен быть повторен для каждого
идентификатора.
Порядок и тип формальных параметров, включая использование запятой с многоточием, должен быть одинаковым во всех объявлениях функции (если они есть) и в определении функции. Типы действительных аргументов в вызовах функции должны совпадать с типами соответствующих формальных параметров до последней запятой с многоточием. Аргументы, которые следуют за запятой с многоточием не проверяются. Формальный параметр может иметь любой фундаментальный, структурный или тип об'единения, тип указателя или массива.
Единственный класс хранения, который можно задать для формального параметра, это register. Предполагается, что необъявленные идентификаторы в скобках, которые следуют за именем функции, имеют тип int. В старой форме определения функции объявления формальных параметров могут следовать в любом порядке.
Идентификаторы формальных параметров используются в теле функции для ссылки на переданные в функцию значения. Эти идентификаторы не могут быть переопределены во внешнем по отношению к телу функции блоке, но они могут быть переопределены во внутренних, вложенных блоках.
В старой форме только идентификаторы, которые появляются в списке идентификаторов, могли быть объявлены формальными параметрами. Функции с переменной длиной списка аргументов должны использовать новую форму прототипа. Программист отвечает за определение числа передаваемых аргументов и за поиск дополнительных аргументов из стека в теле функции. (В Вашем Руководстве по компилятору содержится информация о макросах, которые позволяют это делать наиболее компактным образом.)
Компилятор выполняет обычные арифметические преобразования над каждым формальным параметром и над каждым действительным аргументом, если это является необходимым. После преобразования ни один из формальных параметров не будет короче int, и ни один из формальных параметров не будет иметь тип float. Это значит, например, что объявление формального параметра char имеет тот же эффект, что объявление его int.
Если реализованы ключевые слова near, far и huge, то компилятор может преобразовывать аргумент-указатель в функцию. Выполненное преобразование зависит от размера по умолчанию указателя в программе и наличия или отсутствия списка типов аргументов для функции. Конкретная информация о преобразованиях указателей содержится в Вашем Руководстве по компилятору.
Преобразованные типы каждого формального параметра определяют интерпретацию аргументов, которые вызов функции помещает в стек. Несоответствие типа действительного и формального параметра может вызвать неверную интерпретацию аргументов в стеке. Например, если в качестве действительного аргумента передан 16-битовый указатель, а затем объявлен формальным параметром long, то первые 32 бита стека интерпретируются как формальный параметр long. Эта ошибка вызывает проблемы не только с формальным параметром long, но и со всеми формальными параметрами, которые за ним следуют. Ошибки такого рода обнаруживаются при объявлении прототипов для всех функций.
Пример
Данный пример содержит объявление типа структуры, прототипа для функции match, вызова match и определение прототипа match. Обратите внимание на то, что одно и то же имя student может быть использовано без конфликта и для признака структуры, и для имени переменной структуры.
Функция match объявляется с двумя аргументами: r - указатель на тип struct student; n - указатель на значение типа char.
В определении функции match в списке формальных параметров, который стоит в скобках за именем функции, задаются два формальных параметра с идентификаторами r и n. Параметр r объявлен как указатель на тип struct student, а параметр n объявлен как указатель на значение типа char.
Функция вызывается с двумя аргументами, они оба являются компонентами структуры student. У match имеется прототип, поэтому компилятор проводит проверку типа между действительными аргументами, заданными в прототипе типами и формальными параметрами в определении. Если типы совпадают, то нет необходимости в выдаче предупреждающего сообщения или преобразовании.
Обратите внимание на то, что заданное в качестве второго аргумента в вызове функции имя массива вычисляется в указатель char. Соответствующий формальный параметр также задан, как указатель char, и используется в выражении индекса так, как если бы он был идентификатором массива. Идентификатор массива вычисляется в выражение указателя, поэтому эффект от объявления формального параметра char *n будет тот же, что и при объявлении char n[].
В функции локальная переменная i определяется и используется для управления текущей позицией в массиве. Функция возвращает компонент структуры id, если компонент name соответствует массиву n, в противном случае возвращается 0.
struct student {
char name[20];
int id;
long class;
struct student *nextstu;
} student;
main()
{
/* объявление прототипа функции */
int match ( struct student *r, char *n );
.
.
.
if (match (student.nextstu, student.name) > 0) {
.
.
.
}
}
/* определение функции в стиле прототипа */
match ( struct student *r, char *n )
{
int i = 0;
while ( r->name[i] == n[i] )
if ( r->name[i++] == '\0' )
return (r->id);
return (0);
}
"Тело функции" это составной оператор, который состоит из операторов, задающих конкретное действие функции. Тело функции кроме того может содержать объявления переменных, которые используются этими операторами. (Составные операторы рассматриваются в Разделе 6.3.)
Все объявленные в теле функции переменные имеют класс хранения auto, если явно не задан другой класс. При вызове функции локальным переменным выделяется память и выполняется их локальная инициализация. Управление выполнением программы передается на первый оператор составного оператора и работа происходит до выполнения оператора return или прохода до конца тела функции. Затем управление возвращается на точку, из которой был произведен вызов функции.
Если функция должна возвращать значение, то должен быть выполнен оператор return, содержащий выражение. Если в операторе return нет выражения или оператор return не выполняется, то возвращаемое функцией значение не определено.
Объявление "прототипа функции" задает имя, возвращаемый тип и класс хранения функции. Кроме того, в нем могут быть установлены типы и идентификаторы для некоторых или всех аргументов функции. Прототип имеет тот же формат, что и определение функции, за тем исключением, что у него нет тела и он заканчивается точкой с запятой сразу же после закрывающей скобки. (См. Главу "Объявления" для детального описания синтаксиса объявлений функций.
Можно объявить функцию неявно или использовать прототип функции (иногда называемый "ранним объявлением") для ее явного объявления. прототип это объявление, которое предшествует определению функции. В любом случае возвращаемый тип должен совпадать с заданным в определении функции.
Если вызов функции происходит до ее объявления или определения, то конструируется прототип функции по умолчанию, а возвращаемый тип устанавливается int. Тип и число действительных аргументов используются как основа для объявления формальных параметров. Т.о. вызов функции является ее неявным объявлением, но генерируемый при этом прототип может неадекватно представлять последующее определение или вызов функции.
Прототип устанавливает атрибуты функции таким образом, что вызовы функции, которые предшествуют ее определению (или появлению в других исходных файлах), могут быть проверены на соответствие типов аргументов и возвращаемого значения. Если в прототипе задан спецификатор класса хранения static, то в определении функции также должен быть задан класс хранения static.
Если задан спецификатор класса хранения extern или он не указан совсем, то функция имеет класс extern.
Прототипы функций имеют следующие важные применения:
Список параметров в прототипе это список имен типов, разделенных запятыми, которые соответствуют действительным аргументам в вызове функции. Этот список используется для проверки соответствия действительных аргументов в вызове функции с формальными параметрами в определении функции. Без такого списка нельзя установить несоответствие, и компилятор не сможет выдать диагностическое сообщение. (Проверка типов рассматривается в Разделе 7.4.1, "Действительные аргументы".)
Пример
В данном примере неявно определяется функция intadd, возвращающая значение int, т.к. ее вызов происходит до ее определения. Компилятор создает прототип, используя информацию первого вызова. Дойдя до второго вызова intadd, компилятор видит несоответствие float величины val1 и первого аргумента int в созданном им прототипе. float преобразуется в int и передается в функцию. Заметим, что если бы вызовы функции поменять местами, то созданный прототип ожидал бы первым аргументом float в вызове intadd. При втором вызове функции переменная a будет преобразована, но в момент передачи его значения в intadd будет выдано диагностическое сообщение, т.к. заданный в определении тип int не соответствует типу float в созданном компилятором прототипе.
Функция realadd возвращает величину double вместо int. Следовательно, прототип realadd в функции main необходим, т.к. функция realadd вызывается до своего определения. Обратите внимание на то, что определение realadd соответствует раннему объявлению по возвращаемому типу double.
В раннем объявлении realadd также устанавливаются типы ее двух аргументов. Типы действительных аргументов соответствуют типам, заданным в объявлении, и типам формальных параметров в определении.
main()
{
int a=0, b=1;
float val1=2.0, val2=3.0;
/* прототип функции */
double realadd(double x, double y);
a=intadd(a,b); /* первый вызов intadd */
val1=realadd(val1, val2);
a=intadd(val1,b); /* второй вызов intadd */
}
/* определение функции с формальными параметрами в заголовке */
intadd(int a, int b)
{
return (a+b);
}
double realadd(double x, double y)
{
return (x+y);
}
Синтаксис: выражение([список-выражений])
"Вызов функции" это выражение, которое передает управление и
действительные аргументы, если они есть, функции. В вызове
функции "выражение" вычисляется для получения адреса функции, а
"список-выражений" это список разделенных запятыми выражений.
Значения этих выражений являются действительными аргументами,
передаваемыми в функцию. Если у функции нет аргументов, то список
выражений может быть пустым.
При выполнении вызова функции:
Примечание
Выражения в списке аргументов функции могут быть вычислены в любом порядке, поэтому аргументы, значения которых могут быть изменены побочными эффектами из других аргументов, имеют неопределенные значения. Точка упорядочивания, задаваемая оператором вызова функции, гарантирует только то, что все побочные эффекты в списке аргументов вычисляются до передачи управления на вызванную функцию. Дополнительная информация по точкам упорядочивания содержится в Главе "Выражения и Присвоения".
Единственное требование к вызову функции состоит в том, что выражение до скобок должно вычисляться в адрес функции. Это означает, что функция может быть вызвана через любое выражение указателя на функцию.
Функция вызывается практически аналогично своему объявлению. Например, при объявлении функции задается имя функции, за которым в скобках следует список формальных параметров. Аналогично, при вызове функции нужно задать ее имя, за которым следует список аргументов в скобках. Для вызова функции не требуется оператор адресации (*), т.к. имя функции вычисляется в ее адрес.
При вызове функции с использованием указателя применяются те же принципы. Предположим, например, что указатель функции имеет следующий прототип:
int (*fpointer) (int num1, int num2);
Идентификатор fpointer объявлен для указания на функцию, которая
принимает два аргумента int, соответственно num1 и num2, и
возвращает значение int. Вызов функции с использованием fpointer
может выглядеть так:
(*fpointer) (3,4)
Оператор адресации (*) используется для получения адреса функции,
на которую указывает fpointer. Затем этот адрес используется для
вызова функции. Если прототип указателя на функцию предшествует
вызову, то будет выполнена та же проверка, что и с любой другой
функцией.
Пример 1
В данном примере функция realcomp вызывается в операторе rp=realcomp(a,b);. В функцию передаются два аргумента double. Возвращаемое значение, указатель на величину double, присваивается rp.
double *realcomp(double value1, double value2);
double a, b, *rp;
.
.
.
rp=realcomp(a,b);
Пример 2
В данном примере вызванная в main функция передает целую переменную и адрес функции lift в функцию work:
work (count, lift);
Обратите внимание на то, что адрес функции передается просто
заданием идентификатора функции, т.к. идентификатор функции
вычисляется в выражение указателя. Для использования
идентификатора функции таким способом функция должна быть
объявлена или определена до использования идентификатора, иначе
идентификатор не будет распознан. В этом случае прототип work
задается в начале функции main.
Формальный параметр function в work объявляется как указатель на функцию, которая принимает один аргумент int и возвращает значение long. Обязательно требуется задать скобки вокруг параметра, без них объявление будет задавать функцию, возвращающую указатель на значение long.
Функция work вызывает выбранную функцию следующим образом:
(*function)(i);
Вызванной функции передается один аргумент, i.
main ()
{
/* прототипы функций */
long lift(int), step(int), drop(int);
void work (int number, long (*function)(int i);
int select, count;
.
.
.
select=1;
switch ( select ) {
case 1: work(count, lift);
break;
case 2: work(count, step);
break;
case 3: work(count, drop);
default:
break;
}
}
/* определение функции с формальными параметрами в заголовке */
void work ( int number, long (*function)(int i) )
{
int i;
long j;
for (i=j=0; i<number; i++)
j+=(*function)(i);
}
Действительными аргументами могут быть любые значения фундаментального, структурного типа или типаоб'единения и указателя. Хотя в качестве параметров нельзя передавать массивы или функции, можно передавать указатели на эти элементы.
Все действительные аргументы передаются их значениями. Копия действительного аргумента передается соответствующему формальному параметру. Функция использует эту копию без какого-либо воздействия на переменную, из которой она была получена.
Пойнтеры дают возможность функциям получить доступ к значениям через ссылки на них. Пойнтер на переменную содержит адрес этой переменной, поэтому функция может использовать этот адрес для доступа к значению переменной. Аргументы-указатели позволяют функции получить доступ к массивам и функциям, несмотря на то, что массивы и функции не могут быть переданы в качестве аргументов.
Выражения в функциях вычисляются и преобразуются следующим образом:
Число выражений в списке выражений должно соответствовать числу формальных параметров, если в прототипе функции или ее определении явно не задано переменное число аргументов. В этом случае компилятор проверяет столько аргументов, сколько типов имен содержится в списке формальных параметров, и преобразует их, если это нужно, как это описано выше.
Если список формальных параметров прототипа содержит только ключевое слово void, то компилятор предполагает отсутствие действительных аргументов в вызове функции и отсутствие формальных параметров в ее определении. В противном случае выдается диагностическое сообщение.
Тип каждого формального параметра также подвергается обычным арифметическим преобразованиям. Преобразованный тип каждого формального параметра определяет, как будут интерпретироваться аргументы стека. Если тип формального параметра не соответствует типу действительного аргумента, то данные стека могут быть неверно интерпретированы.
Примечание
Несоответствия типов действительных аргументов и формальных параметров могут вызвать серьезные ошибки, особенно когда их размеры разные. Компилятор может не обнаружить эти ошибки, если не определить полный прототип функции до ее вызова. Если явный прототип не задан, то компилятор строит прототип на основании всей информации, которая имеется в первой ссылке на функцию.
В качестве примера серьезной ошибки рассмотрим вызов функции с аргументом int. если функция определена для приема значения long и определение ее происходит в другом модуле, то сгенерированный компилятором прототип будет не соответствовать определению, но ошибка не будет обнаружена, т.к. отдельные модули будут скомпилированы без диагностических сообщений.
Пример
В данном примере в main объявляется функция swap, с двумя аргументами, соответственно представленными идентификаторами num1 и num2, которые оба являются указательами на значения int. Формальные параметры num1 и num2 в определении прототипа также определяются как указатели на значения типа int. В следующем вызове функции адрес x записывается в num1, а адрес y записывается в num2.
swap (&x, &y)
Теперь для одного места памяти имеются два имени. Ссылки на *num1
и *num2 в swap являются ссылками на x и y в main. Сделанные в
swap присвоения изменят содержимое x и y. Поэтому то и нет
необходимости в операторе return.
Компилятор выполняет проверку аргументов swap, т.к. прототип swap содержит типы аргументов для каждого формального параметра. Идентификаторы в скобках в прототипе и определении могут совпадать, а могут быть и разными. Важно, чтобы типы действительных аргументов совпадали с соответствующими значениями списка формальных параметров как в прототипе, так и в определении.
main ()
{
/* прототип функции */
void swap (int 8num1, int *num2);
int x, y;
.
.
.
swap (&x, &y);
}
/* определение функции */
void swap (int *num1, int *num2)
{
int t;
t=*num1;
*num1=*num2;
*num2=t;
}
При вызове функции с переменным числом аргументов нужно в вызове функции просто задать произвольное число аргументов. Если есть объявление прототипа функции, то переменное число аргументов может быть задано помещением запятой с многоточием (,...) в конце списка формальных параметров или списка типов аргументов (см. Раздел 4.5, "Объявления функций"). Вызов функции должен включать один аргумент для каждого имени типа, объявленного в списке формальных параметров или списке типов аргументов.
Аналогично, список формальных параметров (или список идентификаторов в старой форме) в определении функции может заканчиваться запятой с многоточием для индикации переменного числа аргументов. Дополнительная информация о форме списка формальных параметров содержится в Разделе 7.2, "Определения функций".
Все заданные в вызове функции аргументы помещаются в стек. Число объявленных для функции формальных параметров определяет число аргументов, которые будут взяты из стека и присвоены формальным параметрам. Пользователь сам отвечает за поиск любых дополнительных аргументов в стеке и за определения количества заданных аргументов. Информация о макросах, которые позволяют компактно обрабатывать переменное число аргументов, содержится в Вашем Руководстве по компилятору.
Любая функция в программе на языке С может быть вызвана рекурсивно. Это значит, что она может вызвать саму себя. Компилятор С допускает любое число рекурсивных вызовов функции. Каждый раз при вызове функции выделяется новая область памяти для формальных параметров и переменных auto и register, так что значения предыдущего, не законченного, вызова не перезаписываются. непосредственный доступ к параметрам имеется только в той функции, в которой они были созданы. Для обеспечения независимости функций нет прямого доступа к предыдущим параметрам.
Обратите внимание на то, что для объявленных с классом static переменным не требуется новая память при каждом рекурсивном вызове. Их память имеет периодом действия всю программу. При каждой ссылке на такую переменную организуется доступ к той же области памяти.
Хотя компилятор языка С и не устанавливает пределы числа рекурсивного вызова функций, среда работы сама устанавливает такие практические пределы. Каждый рекурсивный вызов требует дополнительный стек памяти, поэтому слишком много рекурсивных вызовов могут вызвать переполнение стека.