Микропроцессоры

Объявление указателей в языке программирования C-51*

Указатель - это переменная, которая может содержать адрес другой переменной. Указатель может быть использован для работы с переменной, адрес которой он содержит. Использование указателей позволяет реализовать более эффективную обработку массивов, структур, а также реализовывать подпрограммы, которые будут работать над различными областями памяти микроконтроллера. Для этого в подпрограмму нужно только передать начальный адрес обрабатываемой области памяти.

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

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

спецификатор-типа [ модификатор ] *описатель.

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

Примеры объявления указателей на различные типы переменных:

unsigned int * ptr; /* переменная ptr  представляет собой указатель на целую беззнаковую) переменную*/
float * x;        /* переменная  х  указывает  на переменную с плавающей  точкой*/
char *buffer ;   /*объявляется  указатель с именем buffer который указывает на символьную переменную*/

Теперь для того, чтобы начать работать с этими указателями достаточно их инициализировать. Например:

ptr=&A;  //Присвоить адрес переменной A
*ptr=2+2;//Работаем с переменной A
a=&B;
*ptr=3*4;//А теперь работаем с переменной B

В качестве модификаторов при объявлении указателя могут выступать ключевые слова const, data, idata, xdata, code. Ключевое слово const указывает, что указатель не может быть изменен в программе. Размер переменной, объявленной как указатель, зависит от модификатора и используемого вида памяти, для которой будет компилироваться программа. Указатели на различные типы данных не обязательно должны иметь одинаковую длину.

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

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

   float nomer;
   void *addres;
   addres = &nomer;
   (float *)addres ++;
  /* Переменная addres объявлена как указатель на объект любого типа.
     Поэтому ей можно присвоить адрес любого объекта (& - операция
     вычисления адреса). Однако, как было отмечено выше, ни одна
     арифметическая операция не может быть выполнена над указателем,
     пока не будет явно определен тип данных,  на которые он указывает. Это
     можно сделать,  используя операцию приведения типа (float *) для
     преобразования типа указателя addres к типу float. Затем оператор ++
     отдаёт приказ перейти к следующему адресу.*/

В качестве модификаторов при объявлении указателя могут выступать ключевые слова const, data, idata, xdata, code. Ключевое слово const указывает, что указатель не может быть изменен в программе.

Вследствие уникальности архитектуры контроллера 8051 и его производных компилятор С51 поддерживает 2 вида указателей: память-зависимые и нетипизированные.

Нетипизированные указатели

Нетипизированные указатели объявляются точно так же, как указатели в стандартном языке программирования C. Для того, чтобы не зависеть от типа памяти, в которой может быть размещена переменная, для нетипизированных указателей выделяется 3 байта. В первом байте указывается вид памяти переменной, во втором байте - старший байт адреса, в третьем - младший байт адреса переменной. Нетипизированные указатели могут быть использованы для обращения к любым переменным независимо от типа памяти микроконтроллера. Именно поэтому многие библиотечные функции языка программирования C51 используют указатели этого типа, при этом им совершенно неважно, в какой именно области памяти размещаются переменные. Приведем листинг, в котором отображаются особенности трансляции нетипизированных указателей:

stmt level source
   1       char *c_ptr; /* char ptr */
   2       int  *i_ptr; /* int  ptr */
   3       long *l_ptr; /* long ptr */
   4
   5       void main (void)
   6       {
   7     1 char data dj; /*переменные во внутренней памяти данных data */
   8     1 int data dk;
   9     1 long data dl;
  10     1
  11     1 char xdata xj; /*переменные во внешней памяти данных xdata */
  12     1 int xdata xk;
  13     1 long xdata xl;
  14     1
  15     1 char code cj = 9; /*переменные в памяти программ code */
  16     1 int code ck = 357;
  17     1 long code cl = 123456789;
  18     1
  19     1 /*настроим указатели на внутреннюю память данных data */
  20     1 c_ptr = &dj; 
  21     1 i_ptr = &dk;
  22     1 l_ptr = &dl;
  23     1 /*настроим указатели на внешнюю память данных xdata */
  24     1 c_ptr = &xj;
  25     1 i_ptr = &xk;
  26     1 l_ptr = &xl;
  27     1 /*настроим указатели на память программ code */
  28     1 c_ptr = &cj;
  29     1 i_ptr = &ck;
  30     1 l_ptr = &cl;
  31     1 }

ASSEMBLY LISTING OF GENERATED OBJECT CODE
      ; FUNCTION main (BEGIN)
                      ; SOURCE LINE # 5
                      ; SOURCE LINE # 6
                      ; SOURCE LINE # 20
0000 750000 R   MOV   c_ptr,#00H
0003 750000 R   MOV   c_ptr+01H,#HIGH dj
0006 750000 R   MOV   c_ptr+02H,#LOW dj
                      ; SOURCE LINE # 21
0009 750000 R   MOV   i_ptr,#00H
000C 750000 R   MOV   i_ptr+01H,#HIGH dk
000F 750000 R   MOV   i_ptr+02H,#LOW dk
                      ; SOURCE LINE # 22
0012 750000 R   MOV   l_ptr,#00H
0015 750000 R   MOV   l_ptr+01H,#HIGH dl
0018 750000 R   MOV   l_ptr+02H,#LOW dl
                      ; SOURCE LINE # 24
001B 750001 R   MOV   c_ptr,#01H
001E 750000 R   MOV   c_ptr+01H,#HIGH xj
0021 750000 R   MOV   c_ptr+02H,#LOW xj
                      ; SOURCE LINE # 25
0024 750001 R   MOV   i_ptr,#01H
0027 750000 R   MOV   i_ptr+01H,#HIGH xk
002A 750000 R   MOV   i_ptr+02H,#LOW xk
                      ; SOURCE LINE # 26
002D 750001 R   MOV   l_ptr,#01H
0030 750000 R   MOV   l_ptr+01H,#HIGH xl
0033 750000 R   MOV   l_ptr+02H,#LOW xl
                      ; SOURCE LINE # 28
0036 7500FF R   MOV   c_ptr,#0FFH
0039 750000 R   MOV   c_ptr+01H,#HIGH cj
003C 750000 R   MOV   c_ptr+02H,#LOW cj
                      ; SOURCE LINE # 29
003F 7500FF R   MOV   i_ptr,#0FFH
0042 750000 R   MOV   i_ptr+01H,#HIGH ck
0045 750000 R   MOV   i_ptr+02H,#LOW ck
                      ; SOURCE LINE # 30
0048 7500FF R   MOV   l_ptr,#0FFH
004B 750000 R   MOV   l_ptr+01H,#HIGH cl
004E 750000 R   MOV   l_ptr+02H,#LOW cl
                      ; SOURCE LINE # 31
0051 22         RET
      ; FUNCTION main (END)

Память зависимые указатели

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

char data *str;    /*указатель на строку во внутренней памяти данных data */
int xdata *numtab; /*указатель на целую во внешней памяти данных xdata */
long code *powtab; /*указатель на длинную целую в памяти программ code */

Поскольку модель памяти определяется во время компиляции, типизированным указателям не нужен байт, в котором указывается тип памяти микроконтроллера. Поэтому программа с использованием типизированных указателей будет короче и будет выполняться быстрее по сравнению с программой, использующей нетипизированные указатели. Типизированные указатели могут иметь размер в 1 байт (указатели на память idata, data, bdata, и pdata) или в 2 байта (указатели на память code и xdata).

Объявление новых типов переменных

В языке программирования C-51 имеется возможность заранее объявить тип переменной, а затем воспользоваться им при объявлении переменных. Использование заранее объявленного типа позволяет при объявлении переменной сократить его длину, избежать ошибок при объявлении переменных в разных местах программы и добиться полной идентичности объявляемых переменных.

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

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

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

Примеры объявления и использования новых типов:

typedef
 float
 (* MATH)( );     // MATH - новое имя типа, представляющее указатель на  функцию, возвращающую значения типа float

typedef
 char
 FIO[40]                      // FIO - массив из сорока символов


  MATH cos;      // cos  указатель на функцию, возвращающую значения типа  double


 // Можно провести эквивалентное объявление

  float
 (* cos)( );

 FIO person;            //Переменная person - массив из сорока символов


// Это эквивалентно объявлению

   char
 person[40];

При объявлении переменных и типов здесь были использованы имена типов (MATH FIO). Помимо объявления переменных, имена типов могут еще использоваться в трех случаях: в списке формальных параметров при объявлении функций, в операциях приведения типов и в операции sizeof .

Инициализация данных

В языке программирования C-51, как и в других версиях языка C при объявлении переменной ей можно присвоить начальное значение, присоединяя инициатор к описателю. При этом во время запуска Вашей программы в ячейки памяти, соответствующие этим переменным будут записаны начальные значения. Только после этого выполнение программы будет передано подпрограмме main();.

Инициатор переменной начинается со знака "=" и может быть записан в следующих форматах:

Формат 1: = инициатор;
Формат 2: = { список - инициаторов };

Формат 1 используется при инициализации переменных основных типов и указателей, а формат 2 - при инициализации составных объектов.

Примеры присваивания первоначальных значений простым переменным:

char tol = 'N';                                       //Переменная tol инициализируется символом 'N'.
const long megabyte = (1024*1024);

Немодифицируемой переменной megabyte присваивается значение константного выражения, после чего эта переменная не может быть изменена. Отмечу, что для микроконтроллеров семейства MCS-51 внутренняя память является дефицитным ресурсом, поэтому использовать ее для хранения констант нерационально. Лучше объявить переменную с спецификатором типа памяти code.

static int b[2][2] = {1,2,3,4};

Инициализируется двухмерный массив b целых величин, элементам массива присваиваются значения из списка. Эта же инициализация может быть выполнена следующим образом:

static int b[2][2] = { { 1,2 }, { 3,4 } };

При инициализации массива можно опустить одну или несколько размерностей

static int
 b[3] = { { 1,2 }, { 3,4 } };

Если при инициализации указано меньше значений для строк, то оставшиеся элементы инициализируются 0, т.е. при описании

static int b[2][2] = { { 1,2 }, { 3 } };

элементы первой строки получат значения 1 и 2, а второй 3 и 0.

При инициализации составных объектов, нужно внимательно следить за использованием скобок и списков инициализаторов.

Примеры:

struct complex
 {float real;
  float imag;
 }comp[2][3]={{{1,1},{2,3},{ 4, 5}},
              {{6,7},{8,9},{10,11}} 
             };

В данном примере инициализируется массив структур comp из двух строк и трех столбцов, где каждая структура состоит из двух элементов real и imag.

 struct
 complex comp2 [2][3] = { {1,1},{2,3},{4,5},{6,7},{8,9},{10,11} };

В этом примере компилятор интерпретирует рассматриваемые фигурные скобки следующим образом:

  • первая левая фигурная скобка - начало составного инициатора для массива comp2;
  • вторая левая фигурная скобка - начало инициализации первой строки массива comp2[0]. Значения 1,1 присваиваются двум элементам первой структуры;
  • первая правая скобка (после 1) указывает компилятору, что список инициаторов для строки массива окончен, и элементы оставшихся структур в строке comp[0] автоматически инициализируются нулем;
  • аналогично список {2,3} инициализирует первую структуру в строке comp[1], а оставшиеся структуры массива обращаются в нули;
  • на следующий список инициализаторов {4,5} компилятор будет сообщать о возможной ошибке так как строка 3 в массиве comp2 отсутствует.

При инициализации объединения задается значение первого элемента объединения в соответствии с его типом.

Пример:

union tab
 {unsigned char name[10];
  int tab1;
  }pers={'A','H','T','O','H'};

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

Инициализацию массива символов можно выполнить при помощи литеральной строки.

char stroka[ ] = "привет";

Инициализируется массив символов из 7 элементов, последним элементом (седьмым) будет символ '\0', которым завершаются все литеральные строки.

В случае, если задается размер массива, а литеральная строка длиннее, чем размер массива, то лишние символы отбрасываются. Следующее объявление инициализирует переменную stroka как массив, состоящий из семи элементов.

char stroka[5]="привет";

В переменную stroka попадают первые пять элементов литерала, а символы 'т' и '\0' отбрасываются. Если строка короче размерности массива, то оставшиеся элементы массива заполняются нулями. Отметим, что инициализация переменной типа tab может иметь следующий вид:

union tab pers1="Антон";

и, таким образом, в символьный массив попадут символы:

'А','Н','Т','О','Н','\0',

а в остальные элементы будут записаны нули.


* Для тех читателей что вышли на эту страницу по поиску прошу обратить внимание, что здесь рассматривается не стандартный язык программирования С, а язык, адаптированный к микроконтроллерам серии MCS-51. Имеются отличия!

[Назад] [Содержание] [Вперёд]