Функция – подпрограмма,
часть программного кода, исполняемого в стеке.
Пользовательские функции
определяются после директив препроцессора и глобальных объявлений типов,
переменных и констант в файле с исходным кодом или в заголовочном файле.
При этом используется следующий
синтаксис:
Тип_возвращаемого_значения Имя_функции (Список_параметров)
{
//Тело функции
}
ПРИМЕЧАНИЕ
Предварительное объявление функции может отсутствовать. В этом случае она доступна только внутри того файла, в котором определена.
Список
параметров. В функцию могут передаваться параметры, один или несколько —
это идентификаторы, которые могут использоваться внутри функции. Вместо них
подставляются соответствующие значения, указанные при вызове функции (если в
функцию передается более одного параметра, то они отделяются друг от друга
запятыми, как при объявлении, так и при вызове).
Значения,
переданные в функцию, фактически не изменяются, а просто копируются в
параметры, которые в этом смысле выполняют роль локальных переменных. При этом
следует следить за тем, чтобы тип передаваемых значений соответствовал типу
параметров, объявленных в заголовке функции.
Возвращаемые значения.
Возвращать функция может только один параметр.
ПРИМЕЧАНИЕ
Используя указатели можно организовать
возвращение более одного параметра.
В теле функции (в отличии от процедуры) должно быть
ключевое слово return, после которого (через пробел)
указывают возвращаемое значение. При этом все операторы после слова return игнорируются, и происходит возврат в вызывающую
функцию.
Слово
return может также использоваться без указания
возвращаемого выражения. В этом случае оно просто означает выход из функции.
Процедура – функция,
не возвращающая никаких значений. В приведённом в первой главе примере «SOS»
все функции являются процедурами.
Функцию вызывают по ее имени с
указанием в круглых скобках перечня передаваемых параметров (если их нет, то в
скобках ничего не указывается), например:
void Procedure(int n, char с) /* Объявляем локальные переменные n, c (значения
этих переменных передаются из вне) */
{
unsigned char i = 0; // Объявляем локальную переменную i
…
}
int Function()
{
int h; // Объявляем локальную переменную h
char b; // Объявляем локальную переменную b
h = (int) b * 10;
return h;
}
int main()
{
int x = 1000; //
Объявляем переменную x, с начальным значением 1000.
char y =
100; // Объявляем переменную y, с начальным значением 100.
While(1)
{
Procedure (x, y); // Вызываем процедуру, передаём переменным значения n = x, с = y = 100.
x = Function(); //
Изменяем значение x
}
}
Функция main. Любая программа на языке C содержит главную функцию под названием int main(). Эта функция при запуске программы выполняется первой.
Библиотечные
функции. В языке C имеется набор
стандартных функций (см. приложение), также компилятор AVR-GCC имеет набор дополнительных функций.
Классы памяти при объявлении локальных переменных.
Локальные переменные могут быть объявлены внутри функций как принадлежащие
к одному из трех классов памяти:
auto (значение по умолчанию, можно
явно не указывать) — при объявлении переменная не инициализируется никаким
значением (значение — текущее содержимое области памяти, отведенной под
переменную); при выходе из функции переменная удаляется из памяти;
static — статическая переменная доступна
только в пределах функции, хотя память для нее выделяется в пространстве
глобальных переменных; при первом обращении к функции инициализируется нулевым
значением, и после выхода из функции из памяти не удаляется (таким образом, при
последующих обращениях к функции в ней содержится старое значение);
register — аналог автоматической
локальной переменной за тем исключением, что компилятор попытается выделить для
нее не область памяти данных, а рабочий регистр микроконтроллера, что
значительно ускоряет обращение к значению переменной.
Пример
использования статической переменной:
…
int plus5()
{
static int
x;
return x + 5;
}
void main()
{
int y;
у = plus5(); //y = 5
у = plus5(); //y = 10
у = plus5(); //у =
15
}
Прототипы функций. В обычном варианте функции используются только после
их определения, однако бывают случаи, когда функции вызывают друг друга; и
организовать их "правильное" определение невозможно. Обойти подобную
проблему позволяют прототипы функций, которые представляют собой
объявление до определения. Такое объявление представляет собой только заголовок
функции, причем в списке параметров указывают только типы, без идентификаторов,
например:
int f1 (int);
void f2(int, int);
int f1(int x)
void f2(int a, int b)
{
…
}
Прототипы функций часто используются
в заголовочных файлах, включаемых в текст программы с помощью директивы
препроцессора #include.
Рекурсия
— это вызов функцией самой себя. Эта
возможность бывает трудна в понимании, и потому не удивительно, что эксперты по
языку С так любят рекурсивные функции. Пример рекурсивного вызова:
void f1(int n)
{
int x;
fl(x);
}
Несмотря
на сложность восприятия, рекурсия довольно часто используется в стандартных
библиотечных функциях, а также во многих алгоритмах сортировки. Тем не менее,
при программировании микроконтроллеров использование рекурсии, как правило,
чревато проблемами из-за ограниченного объема оперативной памяти. Дело в том,
что при каждом вызове рекурсивной функции часть памяти расходуется на
сохранение данных, помещаемых в стек. Эти данные хранятся там до тех пор, пока
не будет выполнен возврат из функции. Таким образом, когда рекурсивная функция снова
и снова вызывает саму себя, в стеке остается все меньше и меньше свободной
памяти.
Можно
сказать, что в подавляющем большинстве случаев использование рекурсии при
программировании микроконтроллеров — это надежный способ быстрого и
непредсказуемого заполнения стека. Это очень часто приводит к возникновению
ошибочного состояния, называемого переполнением стека. Область стека обычно
размещается в верхней части памяти и растет "вниз", тогда, как
область переменных размещается в нижней части памяти и растет
"вверх". Поэтому если объем данных, помещаемых в стек, превысит
размер области стека, эти данные могут достигнуть области переменных и затереть
собой ее значения. При этом ошибку переполнения стека не всегда легко выявить,
поскольку она может проявляться лишь периодически.
ПРИМЕЧАНИЕ
Подобная проблема может также возникнуть при многократном вложении функций, то есть когда функция f1 вызывает функцию f2, которая вызывает функцию f3. которая вызывает функцию f4 и т.д.