Keil C51: Спецификаторы типа памяти и указатели

Автор: Александр Бельченко
Дата: 22 сентября 2006
Версия: 1.1

Зоопарк типов памяти

Одной из особенностей архитектуры MCS-51 является разделение памяти контроллера на несколько разных типов: внутренняя память данных, внешняя память данных, память программ (в которой могут храниться константные данные). Такое разделение обусловлено с одной стороны гарвардской архитектурой процессора, и с другой стороны реальным физическим воплощением процессора как микросхемы.

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

Указатели

Для того, чтобы писать программы на языке Си, не вдаваясь в детали зоопарка типов памяти процессоров x51, а также чтобы писать переносимые программы, необходимо абстрагироваться от типов памяти. Компилятор C51 предоставляет такую абстракцию в виде так называемых обобщенных указателей (generic pointers).

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

За такое удобство приходится расплачиваться временем выполнения программы и дополнительной памятью:

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

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

Спецификаторы типов памяти

Компилятор C51 расширяет набор ключевых слов языка за счет спецификаторов типов памяти:

  • data — внутренняя память данных, прямая адресация, адреса 0-127
  • idata — внутренняя память данных, косвенная адресация, адреса 0-255
  • bdata — внутренняя память данных, побитно-адресуемая область, адреса 0x20-0x2F
  • xdata — внешняя память данных, адреса 0x0000-0xFFFF
  • pdata — внешняя память данных, адреса 0x00-0x0FF
  • code — память программ, адреса 0x0000-0xFFFF
  • far — внешняя память данных объемом до 16 МБ

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

Размещение переменных

Указание типа памяти для размещения переменных производится за счет добавления спецификатора перед именем переменной:

char  xdata  foo;   // переменная типа char
                    // располагается в области памяти xdata

int  idata  bar;    // переменная типа int
                    // располагается в области памяти idata

Аналогично переменные, которые являются обобщенными указателями, могут быть размещены в различных областях памяти:

char * p1;          // обобщенный указатель на переменную типа char;
                    // сам указатель расположен по умолчанию;
                    // расположение зависит от настроек проекта

int * xdata  p2;    // обобщенный указатель на переменную типа int;
                    // сам указатель размещается в области памяти xdata

Специфицированные указатели

Указатели на данные

Чтобы задать область памяти на которую будет указывать переменная-указатель, необходимо добавить спецификатор перед символом *:

char  xdata  * p3;  // указатель на переменную типа char,
                    // которая располагается в области памяти xdata;
                    // сама переменная-указатель располагается
                    // в области памяти по умолчанию
                    // (зависит от настроек проекта)

int  idata  * p4;   // указатель на переменную типа int,
                    // которая располагается в области памяти idata;
                    // сама переменная-указатель располагается
                    // в области памяти по умолчанию

Указатели на функции

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

typedef void (*PFunc)(void);  // объявление нового типа PFunc,
                              // который представляет собой
                              // обобщенный указатель на функцию
                              // (размер указателя — 3 байта)

typedef void (code *PFunc2)(void);  // 2х байтный указатель
                                    // на функцию

Специфицированные указатели и их явное расположение

Если свести вместе все вышесказанное, то мы увидим как объявить указатель на конкретный тип памяти, который сам будет явно расположен в каком-то типе памяти:

тип куда_указываем * где_располагать имя;

Здесь:

  • тип — тип переменной, на которую указываем
  • куда_указываем — область расположения переменной, на которую указываем
  • где_располагать — область памяти, в которой необходимо расположить переменную-указатель
  • имя — имя переменной-указателя

Примеры:

char xdata * idata foo; // указатель располагается в области idata,
                        // указывает на переменную типа char,
                        // которая располагается в области xdata

typedef void (code *PFunc2)(void);
PFunc2 xdata  pfunc;               // 2х-байтный указатель на функцию;
                                   // сам указатель располагается
                                   // в области xdata

Технические детали

Обобщенные указатели и тройка R3/R2/R1

Для передачи обобщенных указателей, как аргумента некоторой функции, используются регистры процессора R3/R2/R1. При этом в регистре R3 хранится код типа памяти, в регистрах R2/R1 собственно адрес.

R3 R2 R1 тип памяти операция доступа
0x00 R1 idata MOV @R1
0x01 DPH DPL xdata MOVX @DPTR
0xFE R1 pdata MOVX @R1
0xFF DPH DPL code MOVC @A+DPTR

Far-память: адресация до 16 МБ

Как видно из предыдущего пункта, обобщенный указатель не может адресовать все 16МБ внешней памяти типа far, поскольку 4 значения из 256 уже заняты под другие нужды. Т.е. через обобщенный указатель можно адресовать 16МБ – 256КБ.

Хранение обобщенных указателей в памяти

Для хранения указателей в памяти используется та же самая схема Big Endian, которая используется для других многобайтных Си-переменных.

Т.е. по самому младшему адресу хранится код типа памяти (R3), по самому старшему — младший байт указателя (R1).