Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Давыдов В.Г. - Программирование и основы алгоритмизации - 2003

.pdf
Скачиваний:
839
Добавлен:
13.08.2013
Размер:
9.55 Mб
Скачать

Значение сегментного регистра сдвигается влево на четыре разряда (на одну шестнадцатиричную цифру) и к полученному зна­ чению добавляется смещение. Как следует из рис. 56, начальный ад­ рес сегмента всегда является двадцатибитовым числом, а так как сегментный регистр имеет только шестнадцать бит, то недостающие младшие четыре бита всегда подразумеваются равными нулю. Это означает, что сегменты всегда начинаются на границе шестнадцати байт или параграфа (отрезок памяти из смежных шестнадцати байт называется параграфом).

Сегменты памяти могут быть смежными, разделенными, пере­ крываться полностью или частично. Так как сегменты могут пере­ крываться, то одна и та же ячейка памяти может быть адресована более чем одним адресом. Например,

10F0 : 0262 И 10Е0 : 0362

указывают на один и тот же адрес памяти.

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

11.1. Адресация near, far и huge

Специальные ключевые слова

near - ближний г tan: - дальний, hugre - огромный

используются в программах на языках Си/С+н- для модификации оп­ ределений переменных и функций и определяют их размещение в памяти, отличное от стандартного размещения.

Когда эти ключевые слова используются с указателями, то они изменяют размер указателя, который определяется выбранной моде­ лью памяти. Имеется три типа указателей (три типа адресации): near (16 бит), far (32 бита) и huge (32 бита).

Адрес near. Доступ внутри сегмента по умолчанию возможен через шестнадцатибитовое смещение, так как адрес сегмента по умолчанию всегда известен. Например, адрес объекта в сегменте данных по умолчанию получается сложением содержимого шестна­ дцатибитовой величины указателя на объект (смещения) с содержи­ мым регистра сегмента данных DS, сдвинутым влево на четыре би­ та. Это шестнадцатибитовое смещение называется адресом near.

190

Аналогично формируется адрес команды в сегменте команд по умолчанию (вместо регистра DS используется регистр CS).

Доступ к данным или командам из сегментов по умолчанию в языках Си/С++ осуществляется через указатели near:

тип near *near_pointer/

Например,

int near *^_Р^'

Так как для доступа к данным или командам через адрес near требуется только шестнадцатибитовая арифметика, то ссылки near наиболее эффективны.

Адрес far. Когда данные или код программы выходят за преде­ лы сегментов по умолчанию, адрес должен состоять из двух частей: адреса сегмента и адреса смещения. Такие адреса называются адре­ сами ^аг. Доступ вне сегментов по умолчанию осуществляется через указатели Уаг:

тип far *far_pointer;

Например,

±nt far '^f_p;

Указатели y^fr позволяют адресовать всю память, но имеют сле­ дующие особенности.

1. Пусть имеются три указателя/аг - ptrl ^ ptr2, ptr3 - на одну и ту же ячейку памяти:

ptrl

-- 5F20

;:

0210,

ptr2

-- 5F21

;:

0200,

ptr3

- 5F41

::

0000.

-

 

 

Над указателями допустимы операции сравнения и правомер­ ны следующие выражения:

ptrl === ptr2

ptrl == ptr3

ptr2 == ptr3

Однако результатом всех трех сравнений будет значение "ложь", так как операции "==" и "!=" над указателями у^гг использу­ ют все 32 бита указателя как unsigned long int, а не как фактический адрес памяти.

С другой стороны, операции сравнения "<", "<=", ">", ">=" при сравнении указателей у^г используют только 16 бит смещения и для

191

указателей far также не гарантируется правильность выполнения этих операций. Например, вычисление выражений

ptrl > ptr2

ptrl > ptr3

ptr2 > ptr3

Приводит к неожиданному результату: значением выражений будет "истина", хотя в действительности все три указателя адресуют одну

иту же ячейку памяти.

2.Если добавить единицу к указателю У^г 1000:FFFF, то ре­ зультатом будет 1000:0000, а не 2000:0000. Если вычесть единицу из указателя 1000:0000, то результатом будет 1000:FFFF, а не OFFF:OOOF. Таким образом, при увеличении или уменьшении указа­ теля/аг изменяется только смещение. Следовательно, указателемУаг нельзя адресовать данные или код программы, размер которых пре­ вышает 64 Кбайта.

Адрес huge. Адрес huge, так же как и адрес/аг, состоит из ад­ реса сегмента и смещения и занимает 32 бита. Адрес huge в языках Си/С++ задается указателем huge:

тип huge *huge_pointer;

Указатель huge имеет два отличия от указателя Уаг.

1. Указатель huge нормализован и содержит максимально до­ пустимое значение адреса сегмента для определяемого им адреса. Так как сегмент всегда начинается на границе, кратной 16 байтам, то значение смещения для нормализованного указателя будет в пре­ делах от О до F. Например, нормализованной формой указателя 35D2:1253 (определяемый адрес 36F73) будет 36F7:0003. Операции сравнения с указателями huge оперируют со всеми 32 битами и дают правильный результат.

2. Для указателей huge нет ограничений на изменение значения указателя. Если при изменении указателя huge происходит переход через границу 16 байт, то изменяется адрес сегмента.

Например, увеличение на единицу указателя 25B0:000F дает 25В 1:0000 и, наоборот, уменьшение на единицу указателя 2531:0000 дает 25B0:000F. Эта особенность указателя huge позволяет адресо­ вать данные, размер которых превышает 64 Кбайта (занимают более одного сегмента). В языках Си/С++ указатели huge применяют для адресации массивов размером более 64 Кбайт.

192

 

11.2.Стандартные модели памяти

 

для шестнадцатибитной среды DOS

 

Системы программирования Си/С++ для 16-битной среды DOS

предоставляют пять стандартных моделей памяти:

крошечную (tiny);

малую (small);

среднюю (medium);

компактную (compact);

большую (large);

сверхбольшую (huge).

Метод стандартных моделей памяти является наиболее про­ стым способом управления доступом к коду и данным в основной памяти. В этом случае управление памятью осуществляется через режимы (опции) компилятора.

Крошечная модель памяти. Данные, стек, динамическая па­ мять и код программы располагаются в одном и том же сегменте по умолчанию и занимают суммарно не более 64 Кбайт памяти. Для ад­ ресации кода, данных, стека и динамической памяти используются только адреса near , что убыстряет выполнение программы. Исполь­ зуется для построения .сот файлов.

Малая модель памяти. Используется по умолчанию для большинства обычных программ на языках Си/С++. Программа с малой моделью памяти занимает только два сегмента по умолчанию: до 64 Кбайт для кода программы и до 64 Кбайт для данных, стека и динамической памяти программы. Для адресации кода, данных, сте­ ка и динамической памяти используются только адреса near, что убыстряет выполнение программы.

Средняя модель памяти. Используется в программах с боль­ шим объемом кода программы (более 64 Кбайт) и небольшим объе­ мом данных, стека и динамической памяти (не более 64 Кбайт). Средняя модель памяти обеспечивает один сегмент для данных, сте­ ка и динамической памяти программы и отдельный сегмент для ка­ ждого исходного модуля (файла) программы. Это значит, что про­ грамма может занимать до 1 Мбайта л^я кода и до 64 Кбайт для данных, стека и динамической памяти. Поэтому в программах со средней моделью памяти для адресации кода используются адреса far, а для адресации данных - адреса near.

193

Компактная модель памяти. Используется в программах с большим объемом данных и стека программы (более 64 Кбайт до 1 Мбайта) и небольшим объемом кода (не более 64 Кбайт). Компакт­ ная модель памяти обеспечивает один сегмент для кода программы и несколько сегментов для данных и стека программы. Поэтому в программах с компактной моделью памяти для адресации кода ис­ пользуются адреса near, а для адресации данных - адреса far.

Большая модель памяти. Используется в программах с боль­ шим объемом кода, данных и стека программы. Обеспечивает не­ сколько сегментов для кода, данных и стека программы. Это гаран­ тирует до 1 Мбайта суммарной памяти. При этом отдельный элемент данных не может превышать 64 Кбайта. Используются только адре-

са far.

Сверхбольшая модель памяти. Модель аналогична большой модели памяти за исключением того, что в сверхбольшой модели памяти снято ограничение на размер отдельного элемента данных. Для адресации кода адреса far, а для адресации данных - адреса huge.

11.3. Изменение размера указателей в стандартных моделях памяти для шестнадцатибитной среды DOS

Одним из недостатков концепции стандартных моделей памя­ ти является то, что при изменении модели памяти меняются размеры адресов данных и кода. Однако можно подавить задаваемый по умолчанию способ адресации для конкретной модели, используя служебные слова near, far, huge.

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

Функции можно объявлять и определять только с ключевыми словами near и far (ключевое слово huge нельзя применять к функ­ циям). Если ключевое слово near или far предшествует имени функ­ ции, оно определяет, будет ли функция размещаться как near (в сег­ менте кода по умолчанию) или как far (за пределами кода по умол­ чанию).

Если ключевое слово near или far предшествует указателю на функцию, то оно определяет, будет ли для вызова функции исполь­ зоваться адрес near (16 бит) или адрес far (32 бита).

Для определения массивов размером более 64 Кбайт следует использовать ключевое слово huge:

194

^include

<stdio.h>

 

// Массив

huge из 70000 байтов

char hugre

h_arr[

10000 ] ;

Использование операции sizeof для массивов huge имеет осо­ бенности.

printf( "\п Размер массива h_arr: %ld ", sizeof ( h_arr ) ) ;

Напечатается:

Размер массива h_arr:

44 64

(неверный ответ, так как ^/z^o/'возвращает unsigned int в диапазоне 0...65535, а у нас 70000).

Правильный вариант:

printf(

"\п Размер

массива

h_arr:

%ld

",

 

(unsigned,

long inb)

sizeof

( h_arr

) ) ;

11.4.Макроопределения для работы с указателями

вшестнадцатиразрядной среде DOS

Заголовочный файл DOS.H определяет три макроса, облег­ чающих работу с указателями:

FP_OFF(fp)

-

возвращает смещение указателя^;

FP_SEG(fp)

-

возвращает сегмент указателя^;

MK_SEG( S, о ) ~ возвращает длинный указатель, составленный из сегмента s и смещения о , переданных в качестве аргументов.

В качестве аргументов^ в приведенных выше макросах мож­ но использовать не только указатели, но и адреса переменных.

/ /

Применение

макросов

FP__OFF и FP_SEG

 

 

^include

 

 

<stdlo,h>

 

 

 

 

 

^include

 

<dos.h>

 

 

 

 

 

int

mam

(

-void. )

 

 

 

 

 

{

int

 

 

1;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

print

f

(

"Адрес

локальной

переменной:

%p \ л " ,

&i ) ;

 

printf(

 

 

"Адрес

локального

значения:

%04X:%04X

\n"^

 

 

 

 

FP_SEG(

&i ; ,

FP_OFF( &1 )

) ;

 

195

return О;

11.5. Работа с памятью для среды WINDOWS

Приложения для шестнадцатибитной среды Windows (EXE) и Windows (DLL) при компиляции вместо шести могут использовать только одну из следующих четырех стандартных моделей памяти:

малую {smaH)\

среднюю {medium)\

компактную {compact)',

большую {large).

Отличием стандартных моделей памяти для шестнадцатибит­ ной среды Windows (DLL) от среды Windows (EXE) является то, что для данных и динамической памяти используется адресация far во всех моделях памяти.

Другой отличительной особенностью всех приложений Windows для шестнадцатибитной среды является то, что сегмент не содержит реальный адрес памяти. Вместо этого сегмент содержит индекс (селектор), указывающий на строку в таблице (таблице де­ скрипторов), где этот адрес хранится. Для шестнадцатиразрядной же среды DOS процессор аппаратно суммирует значение сегментно­ го регистра с указанным смещением, чтобы получить линейный ад­ рес в оперативной памяти.

Работа

с памятью

в тридцатидвухбитной

среде

WINDOWS.

В тридцатидвухразрядных программах всегда использу­

ется сплошная (непрерывная) память. Управление этой памятью осуществляют интегрированная среда программирования и опера­ ционная система.

12. НОВЫЕ в о з м о ж н о с т и ЯЗЫКА C+-i-, НЕ СВЯЗАННЫЕ С ОБЪЕКТНООРИЕНТИРОВАННЫМ ПРОГРАММИРОВАНИЕМ [3,4]

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

Комментарии. Как уже указывалось ранее, в языке C++ мож­ но использовать два вида комментариев: обычные, оформленные по правилам языка Си, и однострочные, начинающиеся с символов // и продолжающиеся до конца строки. Многочисленные примеры их применения были рассмотрены выше.

Размещение определений данных внутри блока. Напоминаем, что в языке Си все определения локальных данных внутри блока помещаются перед первым исполняемым оператором. В языке же C++ можно (и часто это оказывается более удобным) определять данные в любой точке блока перед их использованием:

/'^

 

Файл

Р36.

СРР

(расширение

 

.СРР

принято

для

файлов

с

текста-

ми

программ

на

C+-h)

.

Ра

змещение

определении

данных

внутри

блока

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

V

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

^include

 

<stdio.h>

 

 

 

 

 

 

 

 

 

 

 

 

 

int

mam ( void )

 

 

 

//

Возвращает

0

при

успехе

 

 

{

//

В

языке C-h-h

"модно"

таким

образом

 

определять

 

и

 

 

 

 

//

 

присваивать

начальное

значение

 

 

управляющей

 

 

 

//

 

переменной

цикла

for

 

<

2;

counterl++

 

)

 

 

fori

 

xnt

counterl

=

0; counterl

 

 

 

//

Переменная

counterl

"видна"^

начиная

с

этой

строки и

 

//

 

до

конца

та±п,

а

не

только внутри

блока

for.

Ей

 

//

 

присваивается

значение

О перед

входом

в

цикл

 

 

{

//

Автоматической

 

переменной

1

присваивается

 

значение

 

 

 

 

 

 

//

 

О при каждом

проходе

 

тела

 

цикла,

 

 

 

 

 

 

±пЬ

i

=- О;

статическая

переменная

j

 

 

 

 

 

//

а

внутренняя

 

 

 

197

 

/ /

 

инициализируется

 

нулем

 

 

 

 

 

 

 

static

±nt

 

 

 

 

 

 

 

 

 

 

 

for(

 

J

= О/

=

0;

counter2

<

5;

counter2++

)

 

 

Int

counter2

 

 

pr±ntf(

"\n

± = %d j = %d",

i-h+r

J++

) ;

 

 

}

 

 

"существует"

до

предыдущей

фигурной

 

скобки

// counter2

 

char

(

 

quit_message[

J

=

"\n

До

свидания!

\n";

printf

"%s",

quit_message

 

)

;

 

 

 

 

 

 

ret-am

0;

 

 

 

 

 

 

 

 

 

 

 

 

}

В качестве упражнения рекомендуем определить, что напеча­ тает данная программа, и проверить результаты Вашего анализа с помощью ЭВМ.

12.1. Прототипы функций. Аргументы по умолчанию

в языке Си наличие прототипов функций необязательно. Такая "снисходительность" часто порождает массу трудно обнаруживае­ мых ошибок, поскольку компилятор не может проверить, соответст­ вуют ли при вызове функций типы передаваемых аргументов и тип возвращаемого значения определению данной функции. Язык Сн-+ более строг: он требует, чтобы в файле, где происходит обращение к функции, причем обязательно до обращения к функции, присутство­ вало либо определение этой функции, либо ее объявление с указани­ ем типов передаваемых аргументов и возвращаемого значения, или, по терминологии языков Си/С+н-, прототип. В последнем случае оп­ ределение функции может находиться в другом файле. Обычно про­ тотипы функций помещают в заголовочный файл, который включа­

ется в компилируемый файл директивой ^include.

В языке

C++ в прототипах функций моэюно задавать значе­

ния аргументов

по умолчанию. Предположим, что написана функция

DrawCircle, которая рисует на экране окружность заданного радиуса с центром в данной точке, и задан ее прототип:

void DrawCircle( ±nt х=100, Int у=100, Int radius=100 ) ;

Тогда вызовы этой функции будут проинтерпретированы, в за­ висимости от количества передаваемых аргументов, следующим образом:

/ / Рисуется окружность с центром в точке (100, 100) и

//радиусом 100

DrawCircle

( ) ;

198

/ /

Рисуется окружность с центром в точке (200,

100) и

//

радиусом 100

 

DrawCircle ( 200 );

 

//

Рисуется окружность с центром в точке (200,

300) и

//радиусом 100

DrawCircle( 200, 300 );

// Рисуется окружность с центром в точке (200, 300) и

//

радиусом 4 00

DrawCircle( 200, 300, 400 );

//

Ошибка: аргументы можно опускать только справа

DrawCircle ( , , 400 );

Значения аргументов

по умолчанию можно задавать не для

всех аргументов, но начинать надо обязательно

"справа":

/ / ОшиОочный прототип

 

 

 

 

void DrawCircle( Int х, int

у=100,

Int

гad );

 

// Ниже даны правильные варианты

 

 

void DrawCircle( int к, int

у=100,

int

radius=100 );

void DrawCircle( int к, int

y, int

radius^lOO

);

12.2. Доступ к глобальным переменным, скрытым локальными переменными с тем же именем

Оператор разрешения области видимости "::" позволяет вос­ пользоваться глобальной переменной в случае, если она скрыта ло­ кальной переменной с тем же именем:

^include <stdio.h>

int

i

= 2;

 

 

int

mam ( void

)

 

 

{

float

i =

5.3f;

 

 

 

 

{

 

 

 

 

cJiax-

*i

= "Hello!";

 

 

printf(

"i-строка = %s i-целое

= %d \n", i, ::i );

 

}

 

 

 

 

zr&tuxm 0;

 

 

 

}

 

 

 

 

 

В результате выполнения программы

получим:

1-строка = Hello! i-целое = 2

199