Что такое функции в программировании на C?

Добавлено 27 мая 2019 в 17:22

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

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

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

Что такое функция?

Функция в C – это группа инструкций, которые работают вместе для реализации определенного типа активности процессора. Во многих случаях функция выполняет одну конкретную задачу, такую как извлечение данных из буфера SPI, настройка таймера таким образом, чтобы он генерировал заданную задержку, или считывание значения из памяти и загрузка его в регистр ЦАП.

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

Рисунок 1 – Что мне нравится в функциях, так это то, что они обеспечивают довольно прямой перевод между диаграммой алгоритма и кодом
Рисунок 1 – Что мне нравится в функциях, так это то, что они обеспечивают довольно прямой перевод между диаграммой алгоритма и кодом

«Компоненты» функции

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

Следующий фрагмент кода показывает нам пример функции.

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.
Фрагмент таблицы ASCII для английского алфавита
СимволДесятичное значениеСимволДесятичное значение
A65a97
B66b98
C67c99
D68d100
E69e101
F70f102
G71g103
H72h104
I73i105
J74j106
K75k107
L76l108
M77m109
N78n110
O79o111
P80p112
Q81q113
R82r114
S83s115
T84t116
U85u117
V86v118
W87w119
X88x120
Y89y121
Z90z122

Функции в аппаратном обеспечении

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

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

Функции на C – это значительное улучшение по сравнению с простыми подпрограммами, используемыми в ассемблере, но принципиально они не отличаются. Когда вы вызываете функцию, программный счетчик процессора получает адрес первой инструкции в машинном коде, которая связана с этой функцией.

Стек вызовов

Раздел памяти, известный как стек вызовов, используется для хранения адреса памяти программ, к которому процессор должен вернуться после выполнения функции. Стек также предоставляет места в памяти для локальных переменных, которые создаются, когда функция вызывается, и используются только внутри функции.

Рисунок 2 – Пример стека вызовов. Обратите внимание, что на диаграмме не указано, сколько байтов требуется для каждого элемента.
Рисунок 2 – Пример стека вызовов. Обратите внимание, что на диаграмме не указано, сколько байтов требуется для каждого элемента.

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

Заключение

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

Теги

MCUВысокоуровневые языки программированияВычисления во встраиваемых системахМикроконтроллерРазработка ПО для встраиваемых системЯзык C

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.