Понятие массивов в программировании на C

Добавлено 26 мая 2019 в 13:33

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

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

В некоторых случаях было бы возможно, хотя и неудобно, хранить эти данные в отдельных переменных – например, ADC_value1, ADC_value2, ADC_value3 и так далее. Однако часто использование отдельных переменных было бы совершенно непрактичным. К счастью, язык C предоставляет простой и очень эффективный способ работы с большими (или маленькими) группами переменных. Возможность, о которой я здесь говорю, называется массивом.

Что такое массив?

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

Рисунок 1 – Для массивов требуется указать тип данных, идентификатор и количество элементов
Рисунок 1 – Для массивов требуется указать тип данных, идентификатор и количество элементов

Вот несколько важных моментов, которые нужно понять о массивах:

  • Все элементы в массиве должны иметь одинаковый тип данных. Например, вы не можете иметь массив, который состоит из переменных char и int.
  • Объем памяти, необходимый для массива, не обязательно совпадает с количеством элементов. Если ваш массив имеет тип данных char и длину 20, он будет использовать 20 байт памяти. Однако если он состоит из 20 элементов с типом long, компилятор должен зарезервировать 80 байтов памяти. Большой массив может занимать значительную часть оперативной памяти микроконтроллера, поэтому убедитесь, что вы не используете излишне большой тип данных. Если все числа в массиве будут меньше или равны 255, объявите массив как unsigned char, а не int.
  • Компилятор будет хранить элементы массива в смежных местах памяти; у вас не будет такой ситуации, когда несколько элементов массива расположены в одном разделе вашей памяти, а затем остальные элементы массива – в совершенно другом разделе. Это означает, что вы можете легко проверить содержимое массива, используя возможности проверки памяти вашего интерфейса отладки (хотя сначала вы должны найти начальный адрес массива).

Как использовать массив

Массив объявлен так:

unsigned char MyArray[100];

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

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

for (n = 0; n < 100; n++)
{
  MyArray[n] = n;
}

Нулевой элемент

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

Рисунок 2 – Первый элемент массива всегда является нулевым
Рисунок 2 – Первый элемент массива всегда является нулевым

Вам нужно проникнуться этим, если вы привыкли к другим языкам программирования, которые используют для первого элемента значение индекса, равное единице, или вы просто находите очень нелогичным, что доступ к «первому» элементу массива осуществляется с помощью числа ноль. Эту важную деталь вы можете запомнить, называя первый элемент нулевым элементом.

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

Последний элемент

Еще один подводный камень, напрямую связанный с проблемой нулевого/первого элемента, заключается в том, что последний элемент массива соответствует индексу, равному размеру массива минус один.

Если вы используете размер массива в качестве индекса, вы получите доступ к памяти, которая не включена в массив. Это может быть что угодно – неиспользуемая память, начало другого массива, часть совершенной не связанной переменной. Это серьезная ошибка, которая может привести к сбою кода чрезвычайно проблемными способами. Будьте особенно осторожны с циклами, в которых индекс массива увеличивается с каждой итерацией. Цикл должен быть спроектирован таким образом, чтобы итерации прекращались до того, как индекс достигнет числа, равного размеру массива.

Инициализация массива

Массивам, как и отдельным переменным, могут быть заданы начальные значения. Последовательность значений заключается в фигурные скобки, а отдельные значения разделяются запятыми. Например:

unsigned int DAC_Config[4] = {0x10, 0x30, 0x87, 0xA1};

Ранее я упоминал, что в объявление массива мы обычно включаем количество элементов. Я сказал «обычно», потому что, если вы включаете последовательность начальных значений, то можете позволить компилятору самому определить необходимую длину.

unsigned int DAC_Config[] = {0x10, 0x30, 0x87, 0xA1};

Это удобно, когда вы используете массив для хранения довольно длинной последовательности символов ASCII, возможно, для целей отладки или для связи через последовательный порт.

unsigned char DataReady_Message[] = "New thermocouple data is available";

Обратите внимание, что символы заключены в двойные кавычки, и фигурные скобки не нужны.

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

unsigned char MyString[] = "TxRDY";
Рисунок 3 – Представление содержимого памяти, созданного объявлением массива, показанным в приведенном выше фрагменте кода
Рисунок 3 – Представление содержимого памяти, созданного объявлением массива, показанным в приведенном выше фрагменте кода

Заключение

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

Теги

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

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

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