Что такое функции в программировании на C?
Данная статья поможет вам понять, что такое функции, зачем они используются, и как они реализованы во встраиваемом аппаратном обеспечении.
Каждая программа на C имеет функцию main()
. Конечно, можно написать успешную программу, в которой единственной функцией является main()
. Я предполагаю, что это было сделано уже много раз, и это правда, что в некоторых простых приложениях никакие функции не нужны.
Однако широкое использование функций свидетельствует о том, что человек, пишущий код, является опытным разработчиком встроенного программного обеспечения. Почему? Потому что функции позволяют нам писать лучший код быстрее, с меньшим количеством работы и меньшим количеством ошибок. Для тех, кто тратит значительную часть своей профессиональной жизни на написание встроенного программного обеспечения (прошивок), это те преимущества, которые нельзя игнорировать. Даже если мы изначально отказываемся от использования функций, потому что кажется, что они требуют больше работы, опыт постепенно учит нас, что выгоды значительно перевешивают затраты.
Что такое функция?
Функция в C – это группа инструкций, которые работают вместе для реализации определенного типа активности процессора. Во многих случаях функция выполняет одну конкретную задачу, такую как извлечение данных из буфера SPI, настройка таймера таким образом, чтобы он генерировал заданную задержку, или считывание значения из памяти и загрузка его в регистр ЦАП.
Однако, безусловно, нет закона, утверждающего, что функция может выполнять только одну задачу. Возможно, вам будет удобно иметь одну функцию, которая обновляет три несвязанных конечных автомата, или вы могли бы написать функцию, которая передает байт через UART, затем проверяет бит состояния до получения байта, а затем включает значение полученного байта в какие-то математические вычисления.
«Компоненты» функции
Функция состоит из имени, списка входных параметров, операторов кода, которые реализуют требуемую функциональность, и типа возвращаемого значения.
Следующий фрагмент кода показывает нам пример функции.
char Convert_to_Lowercase(char UppercaseLetter)
{
if(UppercaseLetter < 65 || UppercaseLetter > 90)
return 0x00;
else
return (UppercaseLetter + 32);
}
Мне нравится делать имена функций очень наглядными. Это делает код более читабельным и помогает вам сохранять ваши мысли организованными.
Инструкции заключены в фигурные скобки; эта часть определения функции называется телом функции. Ключевое слово "return
" используется для выхода из функции и определения того, какие данные должны быть доставлены в ранее выполняющуюся часть кода.
Тип возвращаемого значения, помещенный перед именем функции, определяет тип данных информации, которая будет возвращена. Вполне приемлемо иметь функцию, которая просто выполняет задачу, без необходимости возвращать данные. В этом случае вместо типа данных вы бы использовали ключевое слово "void
".
Передача данных в функцию
Входные параметры, также называемые аргументами, заключаются в круглые скобки и помещаются после имени функции. Функция в C может иметь несколько аргументов, в этом случае они разделяются запятыми. Каждый аргумент должен сопровождаться типом данных.
Во встраиваемых приложениях часто нет необходимости использовать аргументы. Я могу придумать две причины для этого.
Во-первых, встроенное программное обеспечение часто напрямую взаимодействует с аппаратным обеспечением устройства, и, следовательно, функция может получать необходимую информацию из регистров конфигурации, регистров связи или выводов портов.
Во-вторых, простые программы на C, написанные для микроконтроллеров, могут использовать глобальные переменные, то есть переменные, которые присутствуют в программе и доступны любой функции. Насколько я понимаю, использование глобальных переменных в прикладном программировании не рекомендуется, или, может быть, даже более подходящим словом будет «осуждается». Но, на мой взгляд, многие проекты прошивок (встроенного ПО), особенно те, которые полностью написаны одним программистом, могут извлечь выгоду из простоты глобальных переменных.
При определении функции, которая не имеет аргументов, вы можете оставить круглые скобки пустыми или вставить ключевое слово "void
". Теоретически использование "void
" лучше, чем пустые скобки, но в контексте разработки встраиваемых систем, особенно с учетом того, насколько умны современные компиляторы, я не знаю, насколько это действительно важно.
Пошаговый анализ
Давайте кратко рассмотрим определение функции, показанное выше.
- Имя функции,
Convert_to_Lowercase
, ясно указывает на назначение функции: она принимает 8-битное значение, соответствующее заглавной букве ASCII, и возвращает 8-битное значение, соответствующее строчной версии этой же буквы. - Здесь используется один входной параметр. Он имеет тип данных
char
и использует описательный идентификатор. - Возвращаемое значение, как и входной аргумент, является символом ASCII, и, следовательно, тип возвращаемого значения -
char
. - Если входное значение находится за пределами диапазона, соответствующего заглавным буквам ASCII, функция возвращает 0x00, что указывает на ошибку. В противном случае она добавляет 32 к входному значению и возвращает сумму. Если вы незнакомы со значениями ASCII, приведенная ниже таблица поможет вам понять, почему я использую числа 65, 90 и 32.
Символ | Десятичное значение | Символ | Десятичное значение |
---|---|---|---|
A | 65 | a | 97 |
B | 66 | b | 98 |
C | 67 | c | 99 |
D | 68 | d | 100 |
E | 69 | e | 101 |
F | 70 | f | 102 |
G | 71 | g | 103 |
H | 72 | h | 104 |
I | 73 | i | 105 |
J | 74 | j | 106 |
K | 75 | k | 107 |
L | 76 | l | 108 |
M | 77 | m | 109 |
N | 78 | n | 110 |
O | 79 | o | 111 |
P | 80 | p | 112 |
Q | 81 | q | 113 |
R | 82 | r | 114 |
S | 83 | s | 115 |
T | 84 | t | 116 |
U | 85 | u | 117 |
V | 86 | v | 118 |
W | 87 | w | 119 |
X | 88 | x | 120 |
Y | 89 | y | 121 |
Z | 90 | z | 122 |
Функции в аппаратном обеспечении
Так же, как память данных процессора напрямую не поддерживает детали, прикрепленные к переменной в C, память программ процессора намного проще, чем функция в C. Память программ – это длинная последовательность мест хранения, которые не разбиты на категории и не организованы каким-либо полезным способом. Единственное, что идентифицирует конкретное место в памяти программ, – это адрес.
Таким образом, функция в C представляет собой сложный и удобный для программистов способ размещения блоков кода в памяти и направления процессора в блок, который должен быть выполнен. Если вы работали с языком ассемблера, вы знакомы с низкоуровневой реализацией выполнения кода. Каждая инструкция имеет адрес. Мы используем текстовую метку для представления данного адреса, и если мы хотим, чтобы процессор выполнял инструкции по этому адресу, мы говорим ему перейти к соответствующей метке.
Функции на C – это значительное улучшение по сравнению с простыми подпрограммами, используемыми в ассемблере, но принципиально они не отличаются. Когда вы вызываете функцию, программный счетчик процессора получает адрес первой инструкции в машинном коде, которая связана с этой функцией.
Стек вызовов
Раздел памяти, известный как стек вызовов, используется для хранения адреса памяти программ, к которому процессор должен вернуться после выполнения функции. Стек также предоставляет места в памяти для локальных переменных, которые создаются, когда функция вызывается, и используются только внутри функции.
Стек вызовов может служить местом для хранения данных, которые передаются функции в качестве входных параметров, но, насколько я понимаю, компиляторы будут использовать для этого регистры вместо памяти всякий раз, когда это возможно (поскольку регистры работают быстрее).
Заключение
Я надеюсь, что эта статья дала вам хороший обзор структуры и поведения функции, как в программе на C, так и в реальном аппаратном обеспечении процессора. Мы продолжим наше обсуждение функций в C в следующей статье.