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

Сабуров С.В. - Язык программирования C и C++ - 2006

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

Справочник по работе с DOS

Справочник по работе с DOS

Управление памятью в DOS

Нехватка памяти при выполнении

Borland С++ при компиляции не генерирует на диске никаких промежуточных структур данных (записывая на диск только файлы .OBJ). Вместо этого для хранения промежуточных структур данных между проходами используется оперативная память. Поэтому при недостаточном объеме оперативной памяти вам может выводиться сообщение о нехватке памяти. Чтобы решить эту проблему, уменьшите размер функций или разбейте файл с крупными функциями на несколько частей.

Модели памяти

В Borland С++ используется 6 моделей памяти, каждая из которых служит для различных размеров программ и кода.

Регистры процессора

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

Регистры общего назначения

Аккумулятор (математические операции) AX AH AL

Базовый регистр (индексирование) BX BH BL

Счетчик (индексирование) CX CH CL

Регистр данных DX DH DL

Сегментные адресные регистры

CS Сегментный регистр кода

DS Сегментный регистр данных

177

Справочник по работе с DOS

SS Указатель сегмента стека

ES Дополнительный регистр сегмента

Регистры общего назначения

SP Указатель стека

BP Указатель базы

SI Индекс источника

DI Индекс приемника

Общие регистры чаще всего используются для работы с данными. Каждый из них выполняет некоторые специальные функции, которые доступны только ему, например, некоторые математические операции могут использовать только регистр AX, регистр BX может служить базовым регистром, CX применяется инструкцией LOOP и некоторыми строковыми инструкциями, а DX используется некоторыми математическими операциями неявно. Однако во многих операциях можно использовать все эти регистры и заменять один из них на другой.

Сегментные регистры содержат начальный адрес каждого из 4 сегментов. 16 разрядное значение в сегментном регистре для получения 20 разрядного адреса сегмента сдвигается влево на 4 (умножается на 16).

Процессоры имеют также некоторые специальные регистры:

Регистры SI и DI могут выполнять многие функции общих регистров, но могут также использоваться в качестве индексных регистров. Они используются и в регистровых переменных Borland С++.

Регистр SP указывает на текущую вершину стека и представляет смещение в сегменте стека.

Регистр BP — это вспомогательный указатель стека, применяемый для индексирования в стеке с целью извлечения аргументов или локальных динамических переменных.

Функции Borland С++ используют регистр базы (BP) в качестве базового регистра для аргументов и переменных. Параметры имеют положительные смещения от BP, зависящие от модели памяти. При наличии кадра стека BP указывает на

178

Справочник по работе с DOS

сохраненное предыдущее значение BP. Если параметр Standard Stack Frame выключен (Off), то функции без аргументов не используют и не сохраняют BP.

16 разрядный регистр флагов содержит все необходимую информацию о состоянии процессора и результатах последних инструкций.

V

Виртуальный режим

R

Возобновление

N

Вложенная задача

IOP

Уровень защиты ввода вывода

O

Переполнение

D

Направление

I

Разрешение прерывания

T

Прерывание

S

Знак

Z

Признак нуля

A

Вспомогательный перенос

P

Четность

C

Перенос

179

Справочник по работе с DOS

Например, если вы хотите знать, получен ли при вычитании нулевой результат, непосредственно после этой инструкции вам следует проверить флаг нуля (бит Z в регистре флагов). Если он установлен (то есть имеет ненулевое значение), это будет говорить о том, что результат нулевой. Другие флаги, такие, как флаги переноса и переполнения аналогичным образом сообщают о результатах арифметических и логических операций.

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

Регистр флагов не считывается и не модифицируется непосредственно. Вместо этого регистр флагов управляется в общем случае с помощью специальных инструкций (таких, как CLD, STI и CMC), а также с помощью арифметических и логических инструкций, модифицирующих отдельные флаги. И наоборот, содержимое отдельных разрядов регистра флагов влияет на выполнение инструкций (например, JZ, RCR и MOVSB). Регистр флагов не используется на самом деле, как ячейка памяти, вместо этого он служит для контроля за состоянием и управления процессором.

Сегментация памяти

Память микропроцессора Intel имеет сегментированную архитектуру. Непосредственно можно адресоваться к 64К памяти сегменту. Процессор отслеживает 4 различных сегмента: сегмент кода, сегмент данных, сегмент стека и дополнительный сегмент. В сегменте кода находятся машинные инструкции, а в дополнительном сегменте — дополнительные данные. Процессор имеет 4 16 разрядных сегмента (по одному на сегмент) — CS, DS, SS и ES, которые указывают на сегмент кода, данных, стека и дополнительный сегмент соответственно. Сегмент может находиться в любом месте памяти, но начинаться должен по адресу, кратному 10. Сегменты могут перекрываться. Например, все четыре сегмента могут начинаться с одного адреса.

180

Справочник по работе с DOS

Стандартная запись адреса имеет форму «сегмент:смещение», например, 2F84:0546. Начальный адрес сегмента всегда представляет собой 20 битовое число, но так как сегментный регистр содержит только 16 бит, нижние 4 бита полагаются равными 0. Это значит, что сегменты могут начинаться только с тех адресов, у которых последние 4 бита равны 0.

Указатели

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

near (16 бит)

far (32 бита)

huge (32 бита)

segment (16 бит).

В указателях near (ближние указатели) для вычисления адреса используется один сегментный регистр, например, 16 битовое значение указателя функции складывается со сдвинутым влево содержимым регистра кода CS. С такими указателями легко работать.

Указатели far (дальние указатели) содержат не только смещение в сегменте, но и адрес сегмента (другое 16 битовое значение). Такие указатели позволяют иметь несколько сегментов кода и программы, превышающие по размеру 64К. Здесь нужно учитывать, что в операциях == и != используются 32 битовые значения unsigned long, а не полный адрес памяти. В операциях сравнения <=, >=, < и > используется только смещение.

При прибавлении к указателю значения изменяется только смещение. Если смещение превышает FFFF (максимально возможное значение), то указатель возвращается к началу сегмента. При сравнении указателей лучше использовать ближние указатели или указатели huge.

Указатели huge также занимают 32 бита. Аналогично указателям far, они содержат и адрес сегмента и смещение. Однако, чтобы избежать проблем с указателями, такие указатели нормализуются. Нормализованный указатель — это 32 битовый указатель с максимально возможным значением в сегментном

181

Справочник по работе с DOS

адресе. Так как сегмент может начинаться с каждых 16 байт, это означает, что данное смещение будет иметь значение от 0 до 15. Для нормализации указателя он конвертируется в 20 битовый адрес, а затем используются правые 4 бита смещения и левые 16 бит адреса сегмента. Например, 2F84:0532 преобразуется в абсолютный адрес 2FD72, который нормализуется в 2FD7:0002. Нормализация важна по следующими причинам:

Каждому сегментному адресу соответствует при этом только одна возможная адресная пара «сегмент:смещение». Это означает, что операции == и != возвращают корректный ответ.

В операциях сравнения <=, >=, < и > используется при этом полные 32 битовые значения. Нормализация обеспечивает корректность результатов.

Благодаря нормализации смещение в указателях huge автоматически циклически возвращаются каждые 16 байт, но настраивается также и сегмент. Например, при инкрементации 811B:000F результатом будет 811C:0000. Это обеспечивает, что, например, при наличии массива структур типа huge > 64К индексирование массива и выбор поля struct будет работать для структур любого размера.

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

Модели памяти

В 16 разрядных программах Borland С++ вы можете использовать 6 моделей памяти: крохотную, малую, среднюю, компактную, большую и огромную.

Tiny (крохотная)

Эта модель памяти используется в тех случаях, когда абсолютным критерием достоинства программы является размер ее загрузочного кода. Это минимальная из моделей памяти. Все четыре сегментных регистра (CS, DS, SS и ES) устанавливаются на один и тот же адрес, что дает общий размер кода, данных и стека, равный 64К. Используются исключительно ближние

182

Справочник по работе с DOS

указатели. Программы со сверхмалой моделью памяти можно преобразовать к формату .COM (при компоновке с параметром

/t).

Small (малая)

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

Medium (средняя)

Эта модель годится для больших программ, для которых не требуется держать в памяти большой объем данных. Для кода, но не для данных используются указатели far. В результате данные плюс стек ограничены размером 64К, а код может занимать до 1М.

Compact (компактная)

Лучше всего использовать эту модель в тех случаях, когда размер кода невелик, но требуется адресация большого объема данных. Указатели far используются для данных, но не для кода. Следовательно, код здесь ограничен 64К, а предельный размер данных — 1 Мб.

Large (большая)

Модели large и huge применяются только в очень больших программах. Дальние указатели используются как для кода, так и для данных, что дает предельный размер 1 Мб для обоих.

Huge (огромная)

Дальние указатели используются как для кода, так и для данных. Borland C++ обычно ограничивает размер статических данных 64К; модель памяти huge отменяет это ограничение, позволяя статическим данным занимать более 64К.

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

В следующей таблице сведены различные модели и их сравнение друг с другом. Модели часто группируются по модели

183

Справочник по работе с DOS

кода или данных на малые (64К) и большие (16М); эти группы соответственно отражены в столбцах и строках таблицы.

Модели tiny, small и compact относятся к малым моделям кода, поскольку по умолчанию указатели кода являются ближними (near). Аналогичным образом, модели compact, large huge относятся к большим моделями данных, поскольку по умолчанию указатели на данные являются дальними (far).

При компиляции модуля (некоторый исходный файл с несколькими подпрограммами), результирующий код для этого модуля не может превышать 64К, поскольку весь файл должен компилироваться в один кодовый сегмент. Это верно и в том случае, когда вы используете одну из больших моделей памяти (medium, large или huge). Если ваш модуль слишком велик и не помещается в одном кодовом сегменте (64К), вы должны разбить его на несколько файлов исходного кода, скомпилировать каждый из них по отдельности и затем скомпоновать их в одну программу. Аналогичным образом, хотя модель huge и позволяет иметь размер статических данных больше чем 64К, в каждом отдельном модуле статические данные не должны превышать 64К.

184

Справочник по работе с DOS

Программирование со смешанными моделями и модификаторы адресации

Borland C ++ вводит восемь новых ключевых слов, отсутствующих в языке Си стандарта ANSI (near, far, huge, _cs, _ds, _es, _ss и _seg), которые с некоторыми ограничениями и предупреждениями могут использоваться в качестве модификаторов для указателей (и в некоторых случаях, для функций).

В Borland C++ при помощи ключевых слов near, far или huge вы можете модифицировать объявления функций и указателей. Указатели данных near, far и huge рассматривались выше. Объекты far объявляются при помощи ключевого слова far. Функции near запускаются при помощи ближних вызовов (near), а выход из них происходит с использованием ближних команд возврата. Аналогичным образом, функции far вызываются дальними вызовами (far) и выполняют дальний (far) возврат. Функции huge похожи на функции far, за исключением того, что функции huge устанавливают регистр DS в новое значение, тогда как функции far не изменяют значения этого регистра.

Существует также четыре специальных ближних (near) указателя данных: __cs, __ds, __es и __ss. Имеются 16 битовые указатели, конкретно связанные с соответствующими сегментными регистрами. Например, если вы объявите указатель следующим образом:

char _ss *p;

то p будет содержать 16 битовое смещение в сегмент стека.

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

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

185

Справочник по работе с DOS

Типы указателей

 

 

Модель памяти

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

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

Tiny

near, _cs

near, _ds

Small

near, _cs

near, _ds

Medium

far

near, _ds

Compact

near, _cs

far

Large

far

far

Huge

far

far

Указатели сегментов

Вобъявлениях типа указателя сегмента используется __seg.

Врезультате получаются 16 битовые указатели сегментов. Синтаксис __seg следующий:

тип_данных _seg *идентификатор

Например,

int _seg *name

Любое обращение по ссылке через «идентификатор» предполагает смещение 0. В арифметических операциях с указателями выполняются следующие правила:

Нельзя использовать с указателями сегментов операции ++, , + или =.

Нельзя вычитать один указатель сегмента из другого.

При сложении сегментного указателя с ближним (near) указателем результатом будет дальний (far) указатель, который формируется из сегмента, задаваемого сегментным указателем, и смещения из ближнего указателя. Эта операция разрешена только в том случае, если два указателя указывают на один и тот же тип, либо если один из указателей указывает на тип void. Независимо от указываемого типа умножение смещения не происходит.

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

При выполнении операции сложения или вычитания целочисленного операнда и сегментного указателя

186

Соседние файлы в предмете Программирование на C++