
- •Глава 1
- •1.2. Процедурные языки
- •1.3. Языки, ориентированные на данные
- •1.4. Объектно-ориентированные языки
- •1.5. Непроцедурные языки
- •1.6. Стандартизация
- •1.7. Архитектура компьютера
- •1.8. Вычислимость
- •1.9. Упражнения
- •Глава 2
- •2.2. Семантика
- •2.3. Данные
- •2.4. Оператор присваивания
- •2.5. Контроль соответствия типов
- •2.7. Подпрограммы
- •2.8. Модули
- •2.9. Упражнения
- •Глава 3
- •3.1. Редактор
- •3.2. Компилятор
- •3.3. Библиотекарь
- •3.4. Компоновщик
- •3.5. Загрузчик
- •3.6. Отладчик
- •3.7. Профилировщик
- •3.8. Средства тестирования
- •3.9. Средства конфигурирования
- •3.10. Интерпретаторы
- •3.11. Упражнения
- •Глава 4
- •4.1. Целочисленные типы
- •I: Integer; -- Целое со знаком в языке Ada
- •4.2. Типы перечисления
- •4.3. Символьный тип
- •4.4. Булев тип
- •4.5. Подтипы
- •4.6. Производные типы
- •4.7. Выражения
- •4.8. Операторы присваивания
- •4.9. Упражнения
- •Глава 5
- •5.1. Записи
- •5.2. Массивы
- •5.3. Массивы и контроль соответствия типов
- •Подтипы массивов в языке Ada
- •5.5. Строковый тип
- •5.6. Многомерные массивы
- •5.7. Реализация массивов
- •5.8. Спецификация представления
- •5.9. Упражнения
- •Глава 6
- •6.1. Операторы switch и case
- •6.2. Условные операторы
- •6.3. Операторы цикла
- •6.4. Цикл for
- •6.5. «Часовые»
- •6.6. Инварианты
- •6.7. Операторы goto
- •6.8. Упражнения
- •Глава 7
- •7.1. Подпрограммы: процедуры и функции
- •7.2. Параметры
- •7.3. Передача параметров подпрограмме
- •7.4. Блочная структура
- •7.5. Рекурсия
- •7.6. Стековая архитектура
- •7.7. Еще о стековой архитектуре
- •7.8. Реализация на процессоре Intel 8086
- •7.9. Упражнения
- •Глава 8
- •8.1 . Указательные типы
- •8.2. Структуры данных
- •8.3. Распределение памяти
- •8.4. Алгоритмы распределения динамической памяти
- •8.5. Упражнения
- •Глава 9
- •9.1. Представление вещественных чисел
- •9.2. Языковая поддержка вещественных чисел
- •9.3. Три смертных греха
- •Вещественные типы в языке Ada
- •9.5. Упражнения
- •Глава 10
- •10.1. Преобразование типов
- •10.2. Перегрузка
- •10.3. Родовые (настраиваемые) сегменты
- •10.4. Вариантные записи
- •10.5. Динамическая диспетчеризация
- •10.6. Упражнения
- •Глава 11
- •11.1. Требования обработки исключительных ситуаций
- •11.2. Исключения в pl/I
- •11.3. Исключения в Ada
- •11.5. Обработка ошибок в языке Eiffei
- •11.6. Упражнения
- •Глава 12
- •12.1. Что такое параллелизм?
- •12.2. Общая память
- •12.3. Проблема взаимных исключений
- •12.4. Мониторы и защищенные переменные
- •12.5. Передача сообщений
- •12.6. Язык параллельного программирования оссаm
- •12.7. Рандеву в языке Ada
- •12.9. Упражнения
- •Глава 13
- •13.1. Раздельная компиляция
- •13.2. Почему необходимы модули?
- •13.3. Пакеты в языке Ada
- •13.4. Абстрактные типы данных в языке Ada
- •13.6. Упражнения
- •Глава 14
- •14.1. Объектно-ориентированное проектирование
- •В каждом объекте должно скрываться одно важное проектное решение.
- •14.3. Наследование
- •14.5. Объектно-ориентированное программирование на языке Ada 95
- •Динамический полиморфизм в языке Ada 95 имеет место, когда фактический параметр относится к cw-типу, а формальный параметр относится к конкретному типу.
- •14.6. Упражнения
- •Глава 15
- •1. Структурированные классы.
- •15.1. Структурированные классы
- •5.2. Доступ к приватным компонентам
- •15.3. Данные класса
- •15.4. Язык программирования Eiffel
- •Если свойство унаследовано от класса предка более чем одним путем, оно используется совместно; в противном случае свойства реплицируются.
- •15.5. Проектные соображения
- •15.6. Методы динамического полиморфизма
- •15.7. Упражнения
- •5Непроцедурные
- •Глава 16
- •16.1. Почему именно функциональное программирование?
- •16.2. Функции
- •16.3. Составные типы
- •16.4. Функции более высокого порядка
- •16.5. Ленивые и жадные вычисления
- •16.6. Исключения
- •16.7. Среда
- •16.8. Упражнения
- •Глава 17
- •17.2. Унификация
- •17.4. Более сложные понятия логического программирования
- •17.5. Упражнения
- •Глава 18
- •18.1. Модель Java
- •18.2. Язык Java
- •18.3. Семантика ссылки
- •18.4. Полиморфные структуры данных
- •18.5. Инкапсуляция
- •18.6. Параллелизм
- •18.7. Библиотеки Java
- •8.8. Упражнения
5.8. Спецификация представления
В этой книге неоднократно подчеркивается значение интерпретации программы как абстрактной модели реального мира. Однако для таких программ, как операционные системы, коммуникационные пакеты и встроенное программное обеспечение, необходимо манипулировать данными на физическом уровне их представления в памяти.
Вычисления над битами
В языке С есть булевы операции, которые выполняются побитно над значениями целочисленных типов: «&» (and), «|» (or), «л» (xor), «~» (not).
Булевы операции в Ada — and, or, xor, not — также могут применяться к булевым массивам:
type Bool_Array is array(0..31) of Boolean;
Ada |
B2: Bool_Array := (0..15 => False, 16..31 => True);
B1 :=B1 orB2;
Однако само объявление булевых массивов не гарантирует, что они представляются как битовые строки; фактически, булево значение обычно представляется как целое число. Добавление управляющей команды
Ada |
pragma Pack(Bool_Array);
требует, чтобы компилятор упаковывал значения массива как можно плотнее. Поскольку для булева значения необходим только один бит, 32 элемента массива могут храниться в 32-разрядном слове. Хотя таким способом и обеспечиваются требуемые функциональные возможности, однако гибкости, свойственной языку С, достичь не удастся, в частности, из-за невозможности использовать в булевых вычислениях такие восьмеричные или шестнад-цатеричные константы, как OxfOOf OffO. Язык Ada обеспечивает запись для таких констант, но они являются целочисленными значениями, а не булевыми массивами, и поэтому не могут использоваться в поразрядных вычислениях.
Эти проблемы решены в языке Ada 95: в нем для поразрядных вычислений могут использоваться модульные типы (см. раздел 4.1):
Ada |
UI,U2: Unsigned_Byte;
U1 :=U1 andU2;
Поля внутри слов
Аппаратные регистры обычно состоят из нескольких полей. Традиционно доступ к таким полям осуществляется с помощью сдвига и маскирования; оператор
field = (i » 4) & 0x7;
извлекает трехбитовое поле, находящееся в четырех битах от правого края слова i. Такой стиль программирования опасен, потому что очень просто сделать ошибку в числе сдвигов и в маске. Кроме того, при малейшем изменении размещения полей может потребоваться значительное изменение программы.
- Изящное решение этой проблемы впервые было сделано в языке Pascal: использовать обычные записи, но упаковывать несколько полей в одно слово. Обычный доступ к полю Rec.Field автоматически переводится компилятором в правильные сдвиг и маску.
В языке Pascal размещение полей в слове явно не задается; в других языках такое размещение можно описать явно. Язык С допускает спецификаторы разрядов в поле структуры (при условии, что поля имеют целочисленный тип):
C |
int : 3; /* Заполнитель */
int f1 :1;
int f2 :2;
C |
int f3 :2;
int : 4; /* Заполнитель */
int f4 :1;
}reg;
и это позволяет программисту использовать обычную форму предложений присваивания (хотя поля и являются частью слова), а компилятору реализовать эти присваивания с помощью сдвигов и масок:
reg r;
C |
i = r.f2;
r.f3 = i;
Язык Ada неуклонно следует принципу: объявления типа должны быть абстрактными. В связи с этим спецификации представления (representation specifications) используют свою нотацию и пишутся отдельно от объявления типа. К следующим ниже объявлениям типа:
type Heat is (Off, Low, Medium, High);
type Reg is
Ada |
F1: Boolean;
F2: Heat;
F3: Heat;
F4: Boolean;
end record;
может быть добавлена такая спецификация:
Ada |
record
F1 at 0 range 3..3;
F2 at Orange 4..5;
F3at 1 range 1..2;
F4at 1 range 7..7;
end record;
Конструкция at определяет байт внутри записи, a range определяет отводимый полю диапазон разрядов, причем мы знаем, что достаточно одного бита для значения Boolean и двух битов для значения Heat. Обратите внимание, что заполнители не нужны, потому что определены точные позиции полей.
Если разрядные спецификаторы в языке С и спецификаторы представления в Ada правильно запрограммированы, то обеспечена безошибочность всех последующих обращений.
Порядок байтов в числах
Как правило, адреса памяти растут начиная с нуля. К сожалению, архитектуры компьютеров отличаются способом хранения в памяти многобайтовых значений. Предположим, что можно независимо адресовать каждый байт и что каждое слово состоит из четырех байтов. В каком виде будет храниться целое число 0x04030201: начиная со старшего конца (big endian), т. е. так, что старший байт имеет меньший адрес, или начиная с младшего конца (little endian), т. е. так, что младший байт имеет меньший адрес? На рис. 5.6 показано размещение байтов для двух вариантов.
В компиляторах такие архитектурные особенности компьютеров, естественно, учтены и полностью прозрачны (невидимы) для программиста, если он описывает свои данные на должном уровне абстракции.
Однако при использовании спецификаций представления разница между двумя соглашениями может сделать программу непереносимой. В языке Ada 95 порядок битов слова может быть задан программистом, так что для переноса программы, использующей спецификации представления, достаточно заменить всего лишь спецификации.
Производные типы и спецификации представления в языке Ada
Производный тип в языке Ada (раздел 4.6) определен как новый тип, чьи значения и
операции такие же, как у родительского типа. Производный тип может иметь представление, отличающееся от родительского типа. Например, если определен обычный тип Unpacked_Register:
Ada |
record
…
end record;
можно получить новый тип и задать спецификацию представления, связанную с производным типом:
Ada |
for Packed_Register use
record
…
end record;
Преобразование типов (которое допустимо между любыми типами, полученными друг из друга) вызывает изменение представления, а именно упаковку и распаковку полей слов в обычные переменные:
U: Unpacked_Register;
Р: Packed_Register;
-
Ada
U := Unpacked_Register(P);
Р := Packed_Register(U);
Это средство может сделать программы более надежными, потому что, коль скоро написаны правильные спецификации представления, остальная часть программы становится полностью абстрактной.