Модификаторы типов указателей
Turbo С имеет семь модификаторов типа указателей на дaнные: near, far, huge, _cs, _ds, _еs, _ss.
Указатели могут быть представлены в двух формах: "близкие", или nеаг-указатeлu и "далекие", или fаг-указатeлu.
"Близкий" указатель задает только смещение адреса объекта, на который указатель ссылается. В качестве сегментной части адреса объекта используется текущее значение сегментнoгo регистра DS. Поэтому nеаг-указатель занимает в памяти компьютера только два байта и позволяет адресовать не более 64 К байт. "Дaлeкий" указатель задает полный адрес объекта: значение сегмента и значение смещения. Поэтому fа.г-указатель занимает в памяти компьютера 4 байта и позволяет адресоватъ любой байт в адресном пространстве.
Первое слово, рассматриваемое как число без знака, задает смещение, второе значение сегмента адреса. При доступе к объекту данных по значению указателя (т. е. при выполнении операции *) сегмент адреса помещается в сегментный регистр (как правило, это регистр ES). Частный случай fаг-указателя - это huge-указатель, занимающий также 4 байта.
Формат указателя, задаваемый по умолчанию, может быть переопределен явным указанием формы представления. Для этого при описании указателя используются модификаторы near, far и huge. Например:
iпt nеаг ** ptr; char far * strings, huge * norm_ptr;
Явное переопределение формы указателя с far по умолчанию на near позволяет получать более компактный код программы, но применимо только для программ, полный объем дaнныx которых не превышает 64 Кбайт. В противном случае требуются дополнителъныe усилия по переопределению cerментного peгиcтpa DS или использование особых форм near-указателя _еs, _ds, _сs, _ss.
Использование fаг-указателей позволяет обращаться к любому байту адресного пространства компьютера. Весьма популярное применение fаг-указателя – непосредственный доступ к памяти видеоадаптера или специальным областям BIOS.
Пример
Дается простой пример определения типа архитектуры персоналъного компьютера (известно, что предпоследний байт памяти по физическому адресу FFFFEh (ПЗУ ВIOS) содержит индикатор архитектуры персональногo компьютера (значение FFh соответствует компьютеру IВM РС, FEh - компьютеру IВM РС ХТ, FDh - компьютеру IВM PCjr, FCh - компьютеру IВM РС АТ):
#include
void main (void)
{
char * messages[] = {"IBM РС", "IBM РС ХТ',
"IВM PCjr", "IВM РС АТ', "не опознан!",};
char far *ptr = (char far* )0xF000FFFEL;
index = * рtr ^ 0xFF;
printf("Tun комnьютера, сообщаемый BIОS: %s\n",
index <= 3 ? messages[index] : messages[ 4]);
}
Использование far-указателей таит в себе несколько «подвoдных камней». Первый из них - многoзначность представления одного и того же физического адреса памяти. Существует множество пар <сегмент:смещение>, соответствующих одному и тому же адресу в памяти. Например, все три указателя
char far * ptr = 0xb8000000L;
char far * ptr2 = 0xb4004000L;
сhaг far * рtrЗ = 0xb0008000L;
соответствуют одному и тому же физическому адресу - адресу начала видеопамяти цветных адаптеров при их работе в режимах 0 - 6. Однако сравнение указателей друг с другом операцией == дacт всегда значение ложь. При выполнении сравнения faг-указателей операциями >=, >, <=, < используются только смещения, т.е. младшие слова указателей в формате unsigned. Поэтому, например, ptrl < ptr2 дает значение истина, а ptr2 >= ptr3 - ложь. Сравнение же far-указателей операциями != и == выполняется как сравнение чисел long unsigned, а не как сравнение значений физических адресов.
Другая важная проблема использования указателей, в том числе и fаг-указателей, известна в программировании как "перескакивание сегмента" (segment round wrapping). Если к указателю прибавляется какое-то число, изменяется только значение смещения. Если при этом полученное значение смещения должно превысить FFFFh, "перенос" в значение сегмента не происходит, а смещение "перепрыгивает" вновь на границу 0000. Например, если к fаг-указателю 0400:FF00h прибавить lFFh, получается значение 0400: 00FFh, а не1400:00FFh, как можно было бы ожидать. Для nеаr-указателя, равного FFF0h, прибавление 30h дает значение 0020h.
От этих проблем избавлены указатели типа huge. Их часто называют нормализованными. Это означает, что:
1) указатель представляется единственной комбинацией значений <сегмент:смещение>. При этом смещение имеет минимально возможное значение, а сегмент - максимально возможное. Отсюда следует, что значение смещения заключено в диапазоне от 0 дo Fh. Например, физическому адресу памяти FF0FFh соответствует нормализованный указатель FF0F:000Fh. Как следствие этого, операция сравнения == или != выполняется корректно в том смысле, что равные указатели соответствуют равным адресам;
2) при выполнении арифметических операций автоматически восстанавливается нормализация указателя. При выполнении операций сравнения >, >=, < и <= используются все 32 бита hugе-указателя. Это гарантирует корректный результат сравнения. Автоматическая нормализация hugе-указателя при выполнении арифметических операций над указателями позволяет адресовать из С-программы блоки данных размером более 64К байт.
Пример
В качестве примера использования hugе-указателя приведем программу вычисления суммы всех слов области данных BIOS, начиная с адреса F000:0000h. Эга сумма уникальна для серии компьютеров конкретного производителя, архитектуры и состава внешних устройств и может использоваться для самоопределения пpoгpaммы по аппаратуре, например для защиты программ от несанкционированного копирования.
#include
void main ()
{
unsigned hugе *ptr = (unsigned huge* )0xF0000000L;
long unsigned bios_sum = 0;
/* Цuкл пока указатель не paвeн 0000:0000 * /
while( ptr ) bios_sum += * ptr++;
printf("Cyмма кодов ВIОS данного компьютера"\
"( 16с/с) %08х\n", bios_sum);
}
Платой за удобство использования hugе-указателей является значительное замедление скорости работы программы из-за того, что адресная арифметика выполняется специальными функциями.
Особый тип nеаr-указателя в Turbo С - это указатели типа _ds, _еs, _ss и _cs. Такие указатели воздействуют на правила, которые используются компилятором при генерации машинного кода программы. Например, для обычного nеаr-указателя компилятор, встретив операцию «*», генерирует мaшиннyю инструкцию, в которой для доступа к данным используется регистр DS, а значение указателя задает только смещение. Для указателя _es компилятор сгенерирует машинную инструкцию с префиксом переопределения сегмента, и при формировании физического адреса данных процессор будет использовать текущее значение в ES. Для указателя _СS при формировании физического адреса используется регистр CS и т.д. Естественно, что такие указатели могут использоваться, если установлено нужное значение сегментного регистра. Для доступа к сегментным и другим внутренним регистрам процессора можно использовать псевдопеременные _CS, _SS, _ES, _DS и т. п., присваивая им соответствующие значения или их считывая. Отметим, что использование указателей с модификаторами _ds, _es, _ss и _cs требует большой осторожности и опыта.
Пример
В качестве простейшего примера использования указателей с подобными модификаторами приведем намного более производительный, чем предыдущий, вариант программы определения суммы всех слов о6ласти данных BIOS:
#include
void main (void)
{
unsigned _es * ptr = (unsigned _es *) 0х0000;
long unsigned bios_sиm = 0;
_ES = 0xF000;
do{ do
bios_sum += * ptr++;
while( ptr); /* пока адрес не равен ES:0 */
_ES += 0хl000;
}while(_ES); /* пока ES не станет равным нулю */
pintf("Cумма кoдoв ВIOS данного компьютера "\
"( 16с/ с) %08х\n", bios_sum);
}
Приведенная программа более чем в 6 раз превосходит по скорости ранее рассмотренный вариант с использованием huge-указателя. Однако еще раз подчеркнем, что работа с указателями типа _ds, _es, _ss и _cs, равно как и доступ к регистрам процессора через псевдопеременные, требует большой осторожности. Часто значения регистров, в том числе и сегментных, используемых указателями типа _ds, _es, _ss и _cs, изменяются неявно при выполнении операций С-прогpaммы.
Дата добавления: 2016-05-26; просмотров: 1754;