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 |
тип памяти |
операция доступа |
| 0x00 |
— |
R1 |
idata |
MOV @R1 |
| 0x01 |
DPH |
DPL |
xdata |
MOVX @DPTR |
| 0xFE |
— |
R1 |
pdata |
MOVX @R1 |
| 0xFF |
DPH |
DPL |
code |
MOVC @A+DPTR |
Как видно из предыдущего пункта, обобщенный указатель не может адресовать
все 16МБ внешней памяти типа far, поскольку 4 значения из 256 уже заняты под
другие нужды. Т.е. через обобщенный указатель можно адресовать 16МБ – 256КБ.
Для хранения указателей в памяти используется та же самая схема Big Endian,
которая используется для других многобайтных Си-переменных.
Т.е. по самому младшему адресу хранится код типа памяти (R3), по самому
старшему — младший байт указателя (R1).
|
|