Понятие переменных в программировании на C
В данной статье обсуждается суть и использование переменных в языке C в контексте встраиваемых приложений.
Многие из нас слышали слово «переменная» на уроках математики задолго до того, как узнали многое о компьютерном программировании. Математическая переменная – это величина, значение которой неизвестно или не ограничено одним числом. Это использование похоже, хотя и не идентично, понятию переменной в C. Два важных различия: во-первых, в математике для представления переменной мы обычно используем букву, такую как x или y, тогда как в C мы часто используем описательное слово или фразу, такую как temperature, MaxValue или Number_of_Samples. Во-вторых, существуют ситуации, в которых мы используем переменную C для определения величины, которая известна и не предназначена для того, чтобы когда-либо отличаться от исходного значения.
Переменные в аппаратном обеспечении
Для программистов переменные удобны и интуитивно понятны. С другой стороны, для вычислительной техники они не имеют реального значения. Микропроцессоры хранят данные в регистрах и ячейках памяти. Это фундаментальное различие между людьми, которые пишут прошивку, и машинами, которые выполняют прошивку, преодолевается языками высокого уровня, такими как C, который обрабатывает различные детали, связанные с переводом между текстовыми переменными и физической реальностью процессора.
Разработчики встраиваемых систем часто работают с 8-разрядными процессорами. В этих устройствах основной размер данных всегда составляет один байт. Память организована в соответствии с байтами, размер регистров составляет один байт, а сам CPU предназначен для обработки 8-разрядных данных. Это довольно неудобное ограничение, поскольку во многих ситуациях значение переменной будет превышать максимальное значение 8-разрядного числа.
Язык C не ограничивает размер переменной до 8 бит, даже когда вы работаете с 8-разрядным процессором. Это означает, что одна переменная в вашей прошивке может соответствовать нескольким регистрам или ячейкам памяти в аппаратном обеспечении. «Ручное» управление многобайтовыми переменными (т.е. через язык ассемблера) не является моей забавой, но компиляторы не возражают против этого, и они выполняют свою работу очень хорошо.
Определение переменных
Первым шагом в использовании переменной является определение этой переменной. Основными компонентами определения переменной являются тип и имя.
Существует много типов переменных; полный список, а также подробности аппаратной реализации будут зависеть от того, какой компилятор вы используете. Вот некоторые типы:
char
: однобайтовое целое значение со знаком;int
: двух- или четырехбайтовое целое значение со знаком;long
: четырехбайтовое целое значение со знаком;float
: четырехбайтовое значение, которое может иметь числа после десятичной запятой – другими словами, оно не ограничивается целыми числами;bit
: значение переменной может быть ноль или единица.
Следующий код показывает определения переменных, которые состоят только из базового типа и имени (более технический способ ссылка на имя – это «идентификатор»):
int ADC_result;
char ReceivedByte;
float Reference_Voltage;
Инициализация переменной
Во многих случаях хорошей идеей является присвоение переменной начального значения. Это облегчает отладку, и это обязательно, если переменная будет использоваться до того, как ей будет присвоено известное значение. Вы можете инициализировать переменную в определении или в другом месте вашего кода, но включение начального значения в определение – это хороший способ сохранить ваш код организованным и выработать привычку последовательной инициализации, когда это необходимо.
Вот примеры определений переменных, которые включают в себя инициализацию:
int ADC_result = 0;
char ReceivedByte = 0x00;
float Reference_Voltage = 2.4;
Настраиваемые определения переменных
Существуют другие различные ключевые слова, которые могут быть включены в определение переменной. Они используются для более точного определения характера переменной или для инструкций компилятору о том, как реализовать переменную в аппаратном обеспечении.
Следующие ключевые слова могут оказаться полезными в ваших проектах по разработке прошивок:
unsigned
: Как вы могли догадаться, это говорит компилятору интерпретировать переменную как значение без знака, а не как значение со знаком. Я определяю большинство переменных как беззнаковые, потому что мне редко нужны отрицательные числа.const
: Классификатор типаconst
указывает компилятору, что значение переменной не должно изменяться. Как я уже упоминал в начале статьи, иногда значение «переменной» в C не является переменной. Если вы допустите ошибку в своем коде и попытаетесь изменить значение переменнойconst
, компилятор выдаст ошибку.volatile
: Сложные компиляторы не просто берут исходный код и напрямую переводят его в собранную прошивку. Они также пытаются заставить код работать более эффективно, и этот процесс называется «оптимизацией». Однако время от времени это может испортить ваш день, потому что компилятор оптимизирует только на основе кода и не может учитывать аппаратные события, которые взаимодействуют с вашим кодом. Когда переменная имеет спецификатор типаvolatile
, компилятор знает, что он должен быть осторожен с оптимизациями, которые связаны с этой переменной.- типы памяти, такие как
xdata
,idata
иcode
: Эти ключевые слова заставляют компилятор размещать переменную в определенной части памяти микропроцессора. Тип памятиcode
особенно удобен: ресурсы оперативной памяти RAM часто гораздо более ограничены, чем энергонезависимая память программы, а тип памятиcode
позволяет использовать дополнительную память программы для хранения данных, которые используются в вашей программе, но никогда не изменяются.
Вот некоторые примеры:
// переменная принимает значения в диапазоне от 0 до 255
unsigned char UART_byte;
const float PI = 3.14159;
// содержимое регистра может изменяться аппаратным обеспечением,
// поэтому мы используем квалификатор volatile, чтобы избежать
// оптимизаций, которые могли бы заставить программу игнорировать
// события, генерируемые аппаратным обеспечением.
volatile unsigned char ADC_Register;
unsigned char code CalibrationValue = 78;
Использование своих переменных
О том, как использовать переменные после того, как они были определены, можно сказать немного. На самом деле, что касается самой переменной, определение является большей частью работы. После этого вы просто включаете идентификатор переменной в математические операции, циклы, вызовы функций и так далее. Хороший компилятор будет не только обрабатывать детали аппаратной реализации, но и искать способы оптимизации кода с учетом скорости выполнения или размера программы.
Возможно, самая распространенная ошибка, связанная с использованием переменных, – это переполнение. Это относится к ситуации, в которой значение, присвоенное переменной, находится за пределами числового диапазона, связанного с типом данных переменной. Вы должны подумать обо всех возможных сценариях, связанных с данной переменной, а затем выбрать соответствующий тип данных.
Заключение
Основные возможности переменных, предоставляемые языком C, интуитивно понятны и просты, но есть немало деталей, которые помогут вам сделать встраиваемое приложение более надежным и эффективным. Если у вас есть какие-либо вопросы, связанные с переменными C, не стесняйтесь задавать их в комментариях, и мы постараемся включить соответствующую информацию в будущие статьи.