Формулировку того, что такое указатель найти не сложно, а вот понять как им пользоваться не так и просто. Поэтому сделаем попытку разобраться с основными моментами.
pointer_logo
Итак, если сказать простыми словами указатель это адрес объекта. Но много ли эта информация дает для новичка? На мой взгляд нет. Предлагаю посмотреть на практике как это выглядит. Создадим переменную с неким значением, не важно каким, допустим так:

#include <mega8.h>
long g= 130;
 
void main(void)
{
while (1)
      {
           g=1;
      };
}

Запускаем отладчик и направим курсор на переменную g или смотрим значение в окне watch:
pointer_memory2

На текущем шаге, переменная будет равна 130(82 в шестнадцатеричной), а само значение будет храниться в оперативной памяти (SRAM), по адресу 0х15С. В этом не сложно убедиться, если посмотреть содержимое по адресу 0x15С:
pointer_memory1

Итак смотрим в юзер мануал CAVR и видим такую штуку, согласно которой под переменную типа long выделяется 32 бита (4 байта). Это значит что переменная g начинается с адреса 0х15С и заканчивается 0х15F. Т.е. 82 00 00 00 это все относится к переменной g.
pointer_avr_debug_3

Таким образом чтобы прочитать любую переменную в памяти, нужно знать ее адрес и количество байт, которые нужно считать. Адрес нам подскажет указатель, а тип переменной показывает количество выделенной памяти. На основании этого можно применить несколько «фишек»

Пример 1: Допустим, вам нужно 16 битное число передать по uart/spi… За один раз можно отослать только 8бит(1байт, переменная типа char). Как вариант разделим его на две переменные, одна будет содержать старший, байт а вторая младший байт, т.е. если было число 0х9190, одна переменная будет содержать 0х91, а вторая 0х90. Сделать при помощи указателей это просто:

#include <mega8.h>
 
void main(void)
{ 
 
int g = 0x9190; //исходное число
char *i; //указатель
char d = 0; //переменная куда нужно поместить старший байт
char x = 0; //переменная куда нужно поместить младший байт
while (1)
      {
        i = &g; //передаем адрес младшего байта g в указатель
        i++;    //перемещаемся к адресу старшего байта
        d = *i; //передаем значение старшего байта в переменную d 
        x = g; //запись в переменную производится справа налево
      }      //так что запишется только младший байт.
}

В чем прикол? Прикол в том что переменная d типа char, т.е. считается всего один байт, т.е. d станет равной 0х91. Младший байт можно прочитать в некую переменную char x = g; при этом считается только один байт, т.е. младшие разряды.

Пример 2: По аналогии с предыдущим, как собрать из двух char один int. Часто спрашивают, как принимать переменную по uart/spi/…. В чем сложность? Сложность в том что переменные передаются побайтно, т.е. за один раз можно послать число от 0 до 255, не более. Допустим у вам нужно передать большую переменную типа int result = 5428 (0x1534), которая занимает два байта. На передающей стороне все по аналогии с примером 1, разделили на части, отослали, на принимающей стороне собрали в массив char, а значения массива собрали в int. Запись идет справа налево, поэтому сначала 1 элемент массива, потом нулевой.

#include <mega8.h>
 
void main(void)
{       
char byte[2]={0x15,0x34}; //приняли 2 байта по юарту
int result=0;             //сюда мы будем собирать результат   
char * i;                 //указатель
 
i = (char *)(&result);   //считываем адрес куда помещаем
*i = byte[1];  //кинули первую половину значения
i++;          //шагнули на один байт(потому что тип char) в памяти
*i = byte[0];  //кинули вторую половину значения  
 
while (1)
      {          
      }
}

Вот таким хитрым способом можно собрать переменную, без указателя это сделать сложнее.

Пример 3:
Допустим нужно записать текст в переменную, для этого придется создать массив символов:

char hello[5]={'h','e','l','l','o'};

pointer_avr_debug_5

Аналогично этой записи можно поместить строку в память, при помощи указателя:

char *ptr_to_ram="Hello";

pointer_avr_debug_4

Для вывода текста на дисплей в первом случае нужно было бы сделать так:

while(i<5)
{
lcd_putchar(hello[i]);
i++;
}

Во втором случае можно сделать так:

lcd_puts(*ptr_to_ram);

Результат будет один и тот же.

Все еще не убедительно? Тогда как насчет такого, часто возникает вопрос как пишутся библиотеки? Следующий пример должен кое что прояснить.
Пример 4:Допустим есть переменная x, в которую, при помощи некой функции я хочу прочитать температуру:

temp(x); //где то в теле вызываем функцию
//Если определить функцию без указателя то, получится нечто такое: 
void temp(char i)
{
   i = current_temp....; //считываем текущую температуру
   x = i;  //передаем полученное значение в глобальную переменную x
}

Таким образом мы привязаны к тому, что необходимо создавать глобальную переменную «Х». Если захочется изменить название переменной, придется лезть в библиотеку и править библиотеку руками. Неудобно. Теперь рассмотрим другой вариант:

temp(&x); //где то в теле вызываем функцию с адресом переменной
//Если определить функцию с указателем то, получится так: 
void temp(char *i) //принимаем уже не значение переменной, а ее адрес
{
   *i = current_temp; //пишем сразу в адрес указателя
}

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

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

11 комментариев: Указатели

  • i = (char *)(&result); как понимать эту запись?
    в чем разница с этой? unsigned char *pTmp = &tmp;

  • Если компилятор понимает, то разницы никакой. В первой версии CAVR обе строчки скомпилируются одинаково, но во второй версии, вторая строка вывалится с ошибкой, ибо вы пытаетесь преобразовать тип *int в *char

  • Каким компилятором пользовались при написании статьи?

  • Родным CAVR. Эти же штуки прокатывают в keil arm

  • Привет админ. Предистория. Если помните делал автоматику на котел. Как раз была конкретная практика по микроконтроллеру. Начинал тоже как и все, пахопцем, главное что бы лампочкой помигать, реле повключать. Но дошло до дисплея и тут понеслось, как же передать данные, что за звездочка в библиотеках. Начал читать си а потом си++.

  • Вопрос, каким образом закончить передачу данных, тоесть передача данных разная бывает. например раз байт передать, раз надо 4 байта передать. Как цикл организовать? если я правильно понял то цикл wille пишем условия. но какое, что привязивать надо?

  • сделайте переменную счетчик после каждого круга увеличивайте переменную и проверяйте не стала ли она больше нужного

  • то надо будет попрактиковать. так не все понятно. Я еще находил как ви в цикле сравниваете размерность переменной и потом идет отправка . Еще вопросик. Вот хочу я подключить вайвай модуль по spi интерфейсу, Питаюсь сложить как все должно работать. Для мк я напишу отправку и прием данних по spi, это будет мой подчмненный. На компе нужна программа или что , которая будет читать и отправлять данные. Но наверно не все так просто как я думаю. Что думаете по этому поводу?

  • освойте spi на какой нибудь простой микросхеме, а потом можно и wifi

  • с lcd в atmel studio 7 не работает почему?

  • В первом примере — указатель типа char на переменную типа integer, компилятор Cavr ругается ver2_05,
    i = &g; — ругается здесь.
    надо явно тип указывать
    i =(char*) &g; — тогда работает

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Последние комментарии
  • Загрузка...
Счетчик
Яндекс.Метрика