46019 (665326), страница 28
Текст из файла (страница 28)
устанавливает failbit потока str без разрушения прочих битов
int good(); Возвращает не-нулевое значение, если биты
ошибки не устанавливались; в противном случае
возвращает ноль
int eof(); Возвращает не-нулевое значение, если
установлен бит eofbit istream; в противном
случае возвращает ноль.
int fail(); Возвращает не-нулевое значение, если был
установлен один из битов failbit, badbit или
hardfail; в противном случае возвращает ноль.
int bad(); Возвращает не-нулевое значение, если был
установлен один из битов badbit или
hardfail; в противном случае возвращает ноль.
Вы можете также контролировать наличие ошибок, проверяя поток, как если бы он был логическим выражением:
if (cin >> x) return; // ввод в порядке
... // здесь восстановление в случае ошибки
if (!cout) errmsg("Ошибка вывода!");
Эти примеры подчеркивают элегантность С++. Класс ios имеет следующие объявления функции operator:
int operator! ();
operator void* ();
Операцияvoid*() определена как "преобразующая" поток в указатель, который будет равен 0 (ложь), если установлены failbit, badbit или hardfail, и не-нулевому значению в противном случае. (Отметим, что возвращаемый указатель должен использоваться только в логическихпроверках; другого практического применения он не имеет). Перегруженная операция "не" (!) определена как возвращающая не-нулевое значение (истина), если установлены биты ошибки потока failbit, badbit или hardfail; в противном случае она возвращает ноль (ложь).
Использование потоков прошлых версий
Хотя библиотеки stream версий 1.x и iostreamверсии2.0 разделяют многие имена классов и функцийи предлагают многие аналогичные средства, их структуры внекоторых важных областях несколько отличны друг отдруга.Turbo C++, следовательно, реализует два потока с разными библиотеками ифайлами заголовка. Для работы целиком со старыми кодами, использующими потоки, вы должны включить файл stream.h, избегать включения iostream.h и выполнять компоновку со старой библиотекой stream. Дополнительная информация о потоках версии 1.х находится в файле OLDSTR.DOC. Мы такжерекомендуем вам ознакомиться с объявлениями и комментариями в stream.h.
В зависимости от классов и средств, используемых вашими старыми работающими с потоками программами, не исключена их успешная компиляция и выполнение с использованием новой библиотеки iostream.
Рекомендации по переходу к потокам версии 2.0
Ключевое различие между старыми и новыми классами потоков состоит в том, чтокомпоненты public старого класса streambuf теперь, в новом классе streambuf, объявлены как protected. Если ваш старый код с потоками выполняет к таким компонентам прямые ссылки, либо если у вас имеются производные от streambuf классы, определенныена основаниитаких компонентов, то вы должны пересмотреть такие программы, прежде чем они пойдут сбиблиотекой iostream. Другой аспект, способныйповлиять на совместимость, состоит в том, что старый streambufпрямоподдерживал использование символьных массивов для форматирования в оперативной памяти. В случаеiostream эта поддержка предполагается в производном классе strstreambuf, объявляемом в strstream.h.
Старые конструкторы потока, запускающие буферы файлов, например
istream instream(дескриптор_файла)4
должны быть заменены на
ifstream instream(дескриптор_файла);
в программах с использованием iostream.
Старые и новыеклассы потоков по-разному взаимодействуют с stdio. Например, stream.h включает stdio.h, а старые istream и ostream поддерживаютуказатели на структуру stdio FILE. В случае iostream stdio поддерживается через специализированный класс stdiostream, объявленный в stdiostream.h.
В старой библиотеке stream предопределенные потоки cin, cout и cerr связаны непосредственно с файлами структуры FILE в stdio: stdin, stdout и stderr. Вслучаеiostream они подключаются к дескрипторам файлов и используют различные стратегии буферизации. Для того, чтобы избежать проблем с буферизацией при смешанном использовании кодов с stdout и cout, можно записать:
ios::sync_with_stdio();
где выполняется подключение предопределенных потоков к файлам stdio в режиме без буферизации. Отметим, однако, что такой способ значительно замедляет работу cin, cout и cerr, соответственно.
Старая библиотека stream позволяла непосредственно присваивать один поток другому; например,
ostream outs; outs = cout; // только для старых потоков
В случае iostream допустимо присвоение только потоку в левой части выражения присвоения; другими словами, типа istream_withassign или ostream_withassign. Если ваша программа содержит такие присвоения, то их можно либо переписать с использованием ссылок или указателей, либо изменить объявления:
ostream_withassign outs = cout; // только для новых потоков outs << i; // iostream
- 171 -
Глава 4 Модели памяти, операции с плавающей точкой и оверлеи
Данная глава рассматривает три основных вопроса:
- Модели памяти, от tiny до huge. Мы расскажем вам, что они из себя представляют, как выбрать модель памяти и зачем может понадобиться (или почему может не понадобиться) использовать ту или иную конкретную модель памяти.
- Опции операций с плавающей точкой. Как и когда использовать зти опции.
- Оверлеи. Как они работают, и как их использовать.
Модели памяти
Краткий обзор каждой модели памяти см. на стр.194 оригинала.
Turbo C++ предоставляетшесть моделейпамяти, каждая из которых соответствует определенномутипу программы и размеру кодов. Каждая модель памяти по-своему работает с памятью. Почему необходимо разбираться в моделях памяти? Для ответа на этот вопрос следует рассмотреть систему компьютера, с которой вы работаете. В основе блока центрального процессора системы лежит микропроцессор, принадлежащий к семейству микропроцессоров Intel iAPx86; это могут быть процессоры 8088 или 80286, на также и 8086, 80186, 80386или 80486. Пока будем обозначать процессор как 8086. Регистры 8086
Процессор 8086 содержит некоторый показанныйниже набор регистров. Существует, помимо того, еще один регистр - IP (указатель команд) - однако TurboC++ не имеетвозможности непосредственного к нему доступа, и потому он не показан.
Регистры общего назначения
------------- AX \! AH \! AL | ------------------ \! сумматор (матем.операции |
BX \! BH \! BL | \! базовый регистр (индексация) |
CX \! CH \! CL | \! счетчик (индексация) |
DX \! DH \! DL ------------- | \! данные (содердит данные) ------------------ |
Адресные сегментные | регистры |
CS \! \! указатель кодового сегмента
DS \! \! указатель сегмента данных
SS \! \! указатель сегмента стека
ES \! \! указатель вспомог. сегмента
Регистры специального назначения
SP | \! | \! указатель стека |
BP | \! | \! указатель базы |
SI | \! | \! исходный индекс |
DI | \! ------- | \! индекс назначения ------------------------ |
Рис.4.1 | Регистры 8086 |
Регистры общего назначения
Регистрами общего назначения называются наиболее часто используемые для хранения и манипулирования данными регистры. Каждый из них имеет некоторуюспециальную функцию, свойственную только ему. Например,
- Некоторые математические операции могут быть выполнены только с помощью АХ.
- ВХ можно использовать как индексный регистр.
- СХ используется командой LOOP и некоторыми строковыми командами.
-DX неявно используется некоторыми математическими операциями.
Однако, существует множество операций, которые могут равно выполняться всеми этими регистрами; во многих случаяхони взаимозаменяемы.
Сегментные регистры
Сегментные регистры содержат начальные адреса каждого из четырех сегментов. Как будет описано в следующем разделе, 16-битовое значение в сегментном регисире сдвигается влево на 4 бита (т.е. умножается на 16), в результате чего получается 20-битовый адрес данного сегмента.
Регистры специального назначения
8086 имеет несколько регистров специального назначения:
- Регистры SI и DI могут выполнять многие функции регистров общего назначения, плюс они могут быть использованы в качесве индексных регистров.
- Регистр SP указывает на текущую вершину стека и часто содержит смещение для регистра стека.
- Регистр BP - это вторичный указатель стека, обычно используемый для индексирования стека с целью доступа к аргументам или динамическим локальным переменным.
Функции С используют в качестве базового адреса аргументов и динамических локальных переменных регистр - указательбазы (ВР). Параметры имеют положительные смещения относительно ВР, зависящие от модели памяти. ВР всегда указываетна предыдущеесохраненное значение ВР.Функции, не имеющие аргументов, не используют и не сохраняют ВР, если опция StandartStack Frame ("Стандартный стековый фрейм") находится в состоянии Off.
Динамические локальные переменные имеют отрицательные смещения относительно ВР. Смещения эти зависят от того, сколько памяти было уже распределено переменным этого типа.
Регистр флагов
16-битовый регистр флагов содержит всю необходимую информацию о состоянии 8086 и результатах выполнения последних команд.
виртуальный режим 8086
\! возобновление
\! \! вложенная задача
\! \! \! уровень защищенного
\! \! \! ввода/вывода
\! \! \! \! переполнение
\! \! \! \! \! признак
\! \! \! \! \! направления
\! \! \! \! \! \! прерывание
\! \! \! \! \! \! разрешено
\! \! \! \! \! \! \! внутреннее
\! \! \! \! \! \! \! прерывание
\! \! \! \! \! \! \! \! знак
\! \! \! \! \! \! \! \! \! ноль
\! \! \! \! \! \! \! \! \! \!перенос с
\! \! \! \! \! \! \! \! \! \!заемом
\! \! \! \! \! \! \! \! \! \!\! четность
\! \! \! \! \! \! \! \! \! \!\! \! перенос
\! \! \! \! \! \! \! \! \! \!\! \!\!
31 23 \! \!15\! \! \! \! \!\! 7 \!\! \!0
\! \! \! \! \! \! \! \! \! \! \! \! \! \! \!V\!R\! \!N\!IOP\!O\!D\!I\!T\!S\!Z\! \!A\! \!P\! \!C\!
-----------------------------------------------------------
\_____________________________/ \_____/ \_________________/
только 80386 80286 все процессоры 80х86
80386
Рис.4.2 Регистр флагов 8086
Например, вам понадобилось узнать, равен ли результат операции вычитания нулю; для этого вам достаточно проверить флаг нуля (бит Zрегистра флагов) непосредственно сразу же после выполнения команды; если данный флаг установлен, то результат действительнобыл равен нулю. Прочие флаги, такие как фоаги переноса или переполнения, аналогичным образом сообщают вам о результатах выполнения тех или иных математических или логических операций.
Прочие флаги контролируют режимы работы 8086. Флаг направления управляет направлением смещения строковых команд, а флаг прерываний управляет тем, разрешено ливнешним аппаратным устройствам, таким как клавиатура или модем, временно приостанавливать выполнение текущего кода для обслуживания возникающих у нихпотребностей. Флаг внутренних прерываний используется только программным обеспечением, предназначеннымдля отладки другого программного обеспечения.
Обычно регистр флагов не считываетсяи не модифицируется непосредственно. Обычно обращения к этому региструвыполняются посредством специальных ассемблерных команд (таких как CLD, STI и CMC), а такжепри помощи арифметических илогических команд, модифицирующих конкретные флаги. Подобным же образом, содержимое конкретных битов регистра флагов влияет на работу таких команд, как JZ, RCRи MOVSB. Регистр флагов фактическиникогда не используется как адрес памяти, а содержит данные о состоянии и управлении 8086.
Сегментация памяти
Микропроцессор Intel 8086 имеет сегментированную архитектуру памяти. Он имеет общий объем памяти 1 Мб, но позволяет одновременно адресовать только 64 Кб памяти. Такой участок памяти называется сегментом; отсюда и название "сегментированная архитектура памяти".
- 8086 позволяет работу с четырьмя различными сегментами: кодовым, данных, стека и вспомогательным. Кодовый сегмент содержит машинные команды программы; в сегменте данных хранится информация; сегмент стека имеет, разумеется, организацию и назначение стека; вспомогательный сегмент используется для хранения некоторых вспомогательных данных.
- 8086 имеет четыре 16-битовых сегментных регистра (один на каждый сегмент) с именами CS, DS, SS и ES; они указывают на кодовый сегмент, сенгмент данных, стека и вспомогательный сегмент, соответственно.
- Сегмент может располагаться в произвольном адресе памяти - практически везде. По причинам, которые станут вам ясны по мере ознакомления с материалом, сегмент должен располагаться в памяти, начиная с адреса, кратного 16 (десятичное).
Вычисление адреса
Полный адрес в8086 состоит из двух 16-битовых значений: адреса сегмента и смещения. Предположим, что адрес сегмента данных - т.е. значение в регистре DS -- равен 2F84 (шестнадцатиричное) и вы желаете вычислить фактический адрес некоторого элемента данных, который имеет значение 0532 (основание
16) от начала сегмента данных; как это сделать?
Вычисление адреса будет выполнено следующим образом: нужно сдвинуть влево значение сегментного регистра на 4 бита (это эквивалентно одной шестнадцатиричной цифре), а затем сложить с величиной смещения.
Полученное 20-битовое значениеи есть фактический адрес данных, как показано ниже:
регистр DS (после сдвига):0010 1111 1000 0100 0000 = 2F840 смещение: 0000 0101 0011 0010 = 00532
--------------------------------------------------------
Адрес:0010 1111 1101 0111 0010 = 2FD72
Участок памяти величиной 16 байт называетсяпараграфом, поэтому говорят, что сегмент всегда начинаетсяна границе параграфа.
Начальный адрес сегмента всегда является20-битовым числом, но сегментный регистр имеет всего 16 битов - поэтому младшие 4 бита всегда предполагаются равными нулю. Это означает - как было уже сказано - что начало сегмента можетнаходиться только в адресах памяти, кратных 16, т.е. адресах, в которых последние 4 бита (или один шестнадцатиричный разряд) равен нулю. Поэтому если регистр DS содержит значение 2F84, тофактически сегмент данных начинается в адресе 2F840.
Стандартная запись адреса имеет форму сегмент:смещение; например, предыдущий адрес можно записать как 2F84:0532. Отметим, что поскольку смещения могут перекрываться, данная пара сегмент:смещение неявляется уникальной; следующие адреса относятся к одной и той же точке памяти:
0000:0123
0002:0103
0008:00A3
0010:0023
0012:0003
Сегментымогут (но не должны) перекрываться. Например, все четыре сегмента могут начинаться с одного и того же адреса,что означает, что вся ваша программа в целом займет не более 64 Кб - но тогда в пределах этой памяти должны поместиться и коды программы, и данные, и стек.
Указатели
Какое отношение имеют указатели к моделям памяти и Turbo C++? Самое непосредственное. Тип выбранной вами модели памяти определяет тип по умолчанию указателей, используемых для кода и данных (хотявы можете явно объявить указатель или функцию как имеющие тот или иной конкретный тип, независимо от текущей модели памяти). Указатели бывают четырех разновидностей: near (16 битов), far (32 бита), huge (также 32 бита) и segment (16 битов).
Ближние указатели (near)
16-битовый (near) указатель для вычисления полного адреса связывается с одним из сегментных регистров; например, указатель функции складывает свое 16-битовоезначение со сдвинутым влево содержимым регистра кодового сегмента (CS). Аналогичным образом, ближний указатель данных содержит смещение в сегменте данных, адресуемом регистром сегмента данных (DS). С ближнимиуказателями легкоманипулировать, поскольку все арифметические операции с ним (например, сложение) можно выполнять безотносительно к сегменту.
Дальние указатели (far)
Дальний (32-битовый) указатель содержит не только смещение относительно регистра, но также и (в остальных 16 битах) адрес сегмента, который затем должен быть сдвинут влево и сложен со значением смещения. Использование дальних указателей позволяет иметьв программе несколько кодовых сегментов; это, в свою очередь, позволяет программе превышать 64К. Можно также адресовать более 64К данных.
При использовании дальних указателей для адресации данных вам следует учитывать некоторые потенциальные проблемы, которые могут возникать при манипулировании такими указателями. Какобъяснялось в разделе, посвященном вычислениямадреса, может существовать множество пар типа сегмент:смещение, адресующих одну и ту же точку памяти. Например, дальние указатели 0000:0120, 0010Ж0020 и 0012:0000 разрешаются к одному и тому же 2-битовому адресу. Однако, если у вас были бы три переменных типа дальнего указателя - a,b и c, содержащих соответственно три указанных значения, то следующие выражения все давали бы значение "ложь":
if (a == b) . . .
if (b == c) . . .
if (a == c) . . .
Аналогичная проблема возникает, когда вам требуется сравнивать дальние указатели при помощи операций >, >=, < и <=. В этих случаях в сравнении участвует только смещение (как unsigned); при указанных выше значениях a, b и cследующие выражения дадут значения "истина":
if (a > b) . . .
if (b > c) . . .
if (a > c) . . .
Операции равенства (==) и неравенства (!=) используют 32-битовые значения как unsigned long (а не в виде полного адреса памяти). Операции сравнения(=, ) используют только смещение.
Операции== и != требуют все 32 бита, что позволяет компьютеру выполнять сравнение с пустым (NULL) указателем (0000:0000). Если дляпроверки равенства использовалось только значение смещения, то любой указатель со смещением 0000 будет равен пустому указателю, что явно несовпадает с тем, что вы хотели получить.
Важное замечание
При сложении некоторого значения сдальним указателем изменяется только смещение. Если слагаемое настолько велико, что сумма превышает FFFF (максимально возможная для смещения величина), то указатель перейдет снова к началу сегмента. Например, если сложить 1 и 5031:FFFF, то результат будет равен 5031:0000 (а не 6031:0000). Подобным же образом, при вычитании 1 из 5031:0000 получится 5031:FFFF (а не 5030:000F).
Если вам понадобится выполнить сравнение указателей, то безопасный способ состоит в том, чтобы либо использовать для этого ближние указатели -все содним адресомсегмента - либо описываемые ниже указатели huge.
Указатели huge
Указатели huge также имеют размер 32 бита. как и указатели far, они содержат одновременно адрес сегмента и смещение. Однако, в отличие от дальних указателей, они нормализованы, что позволяет избежать проблем, связанных с дальними указателями.
Что такое нормализованный указатель? Это 32-битовый указатель, который содержитв своем адресе сегмента максимально возможное там значение. Поскольку сегмент может начинаться через каждые 16 байт (10 при основании 16), это означает, что величина смещения будет равна числу от 0 до 15 (от 0 до F с основанием 16).
Для нормализации указателя он преобразуется к20-битовому адресу, после чего правые 4 бита берутся в качестве смещения, а левые 16 битов - как адрес сегмента. Например, указатель 2F84:0532 можно сначала преобразовать к абсолютному адресу 2FD72, после чего получить нормализованный указатель2FD7:0002. Приведемеще ннесколько указателей с нормализованными значениями:
0000:01230012:0003
0040:00560045:0006
500D:9407594D:0007
7418:D03F811B:000F
Существует три причины, заставляющие всегда хранить указатель huge в нормализованном виде:
1. Поскольку в таком случае любому заданному адресу памяти соответствует только один возможный адрес в виде сегмент:смещение типа huge. Это означает, что для указателей huge операции == и != всегда будут возвращать правильное значение.
2. Кроме того, операции >, >=, < и <= работают с полным 32-битовым значением указателя huge. Нормализация гарантирует в данном случае корректность результата.
3. И наконец, вследствие нормализации смещение в указателе huge выполняет автоматический переход через 16 но вотличие от дальних указателей, переход затрагивает и сегмент. Например, при инкременте 811B:000Fрезультатбудет равен 811C: 0000; аналогичным образом, при декременте 800C:0000 получится 811B:000F. Эта особенность указателей huge позволяет манипулировать соструктурами данных сразмером более 64К. Гарантируется, например, что если у вас имеется массив структур типа huge, превышающий 64К, индексация этого массива и выбор поля структуры всегда будут выполняться правильно, независимо от размера структуры.
Использование указателей huge имеет свою цену: увеличение времени обработки. Арифметические операции с указателями huge выполняются при помощи обращений к специальным подпрограммам. Вследствие этого арифметические вычисления занимаютсущественно больше времени по сравнению с вычислениями для указателей far или near.
Шесть моделей памяти
Turbo C++ работает с шестью моделями памяти: tiny, small, medium, compact, large и huge. выбор модели памяти определяется требованиями вашей программы.Ниже приводятся краткие описания каждой из них:
Tiny
Эта модель памяти используется в тех случаях, когда абсолютным критериемдостоинства программыявляется размер ее звгрузочного кода.
Как вы уже поняли, это минимальная из моделей памяти.Все четыре сегментных регистра (CS, DS, SS и ES) устанавливаются на один и тот же адрес, что дает общий размер кода, данных и стека, равный 64К. Используютсяисключительноближние указатели. Программы с моделью памяти tuny могут быть преобразованы к формату .COM при компоновке с опцией /t.
Small
Эта модель хорошо подходит для небольших прикладных программ.
Сегменты кода и данных расположены отдельно друг от друга и не перекрываются, что позволяет иметь 64К кода программы и 64К данных и стека. Используются только ближние указатели.
Medium
Годится для больших программ,для которых не требуется держать в памяти большой объем данных.
Для кода, но не для данных используются дальние указатели. В результате данные плюс стек ограничены размером 64К, а код может занимать до 1 Мб.
Compact
Лучше всего использовать в тех случаях, когда размер кода невелик, но требуется адресация большого объема данных.
Ситуация, противоположная относительно модели Medium: дальние указатели используются для данных, но не для кода. Следовательно, код здесь ограничен 64К, а предельный размер данных - 1 Мб.
Large
Модели large иhuge применяются только в очень больших программах.
Дальние указатели используются как для кода,так идля данных, что дает предельный размер 1 Мб для обоих.
Huge
Дальние указатели используютсякак для кода, так и для данных. Turbo C++ обычно ограничиваетразмерстатических данных 64К;модельпамятиhuge отменяетэто ограничение, позволяя статическим данным занимать более 64К.
Для выбора любойиз этих моделей памяти вы должны либо воспользоваться соответствующей опцией меню интегрированной среды, либо ввести опцию при запуске компилятора командной строки.
Следующие иллюстрации(Рис.4.3 - 4.8) показывают,как выполняется распределение памяти для всех шести моделей памяти Turbo C++.
Сегментные регистры: Размер сегмента:
Младший ^ CS,DS,SS-----> --------------------------- адрес | / \! _TEXT класс 'CODE' \! \
| | \!код \! |
| | \!-------------------------\! |
| | \! _DATA класс 'DATA' \! |
| | \!инициализированные данные\! |
| | \!-------------------------\! |
| | \! _BSS класс 'BSS' \! |
|DGROUP/ \!неинициализирован. данные\! \ до 64К
|\ \!-------------------------\! /
| | \! куча| \! |
| | \!v \! |
| | \!-------------------------\! | Свободная
| | \! \!-|--область
|SP(TOS)--|->\!-------------------------\! | памяти
| | \!^ \! |
Старший | \ \! стек| \!/
адрес v Начало SP----> ---------------------------
Рис.4.3 Сегментация для модели памяти tiny
Сегментные регистры: Размер сегмента:
Младший ^ CS-----------> --------------------------- адрес | \! _TEXT класс 'CODE' \!
| \!код \! до 64К
| DS,SS--------> \!-------------------------\!
| / \! _DATA класс 'DATA' \!\
| | \!инициализированные данные\! |
| | \!-------------------------\! |
| | \! _BSS класс 'BSS' \! |
|DGROUP/ \!неинициализирован. данные\! \ до 64К
|\ \!-------------------------\! /
| | \! куча| \! |
| | \!v \! |