Глава 11 готова

This commit is contained in:
Alexander Zhirov 2023-03-03 18:47:07 +03:00
parent 3df2c16137
commit 452a985409
1 changed files with 61 additions and 58 deletions

View File

@ -2,33 +2,36 @@
- [11.1. Пакеты и модули](#11-1-пакеты-и-модули)
- [11.1.1. Объявления import](#11-1-1-объявления-import)
- [11.1.2. Базовые пути поиска модулей]()
- [11.1.3. Поиск имен]()
- [11.1.4. Объявления public import]()
- [11.1.5. Объявления static import]()
- [11.1.6. Избирательные включения]()
- [11.1.7. Включения с переименованием]()
- [11.1.8. Объявление модуля]()
- [11.1.9. Резюме модулей]()
- [11.2. Безопасность]()
- [11.2.1. Определенное и неопределенное поведение]()
- [11.3.2. Атрибуты @safe, @trusted и @system]()
- [11.3. Конструкторы и деструкторы модулей]()
- [11.3.1. Порядок выполнения в рамках модуля]()
- [11.3.2. Порядок выполнения при участии нескольких модулей]()
- [11.4. Документирующие комментарии]()
- [11.5. Взаимодействие с C и C++]()
- [11.5.1. Взаимодействие с классами C++]()
- [11.6. Ключевое слово deprecated]()
- [11.7. Объявления версий]()
- [11.8. Отладочные объявления]()
- [11.9. Стандартная библиотека D]()
- [11.10. Встроенный ассемблер]()
- [11.10.1. Архитектура x86]()
- [11.10.2. Архитектура x86-64]()
- [11.10.3. Разделение на версии]()
- [11.10.4. Соглашения о вызовах]()
- [11.10.5. Рациональность]()
- [11.1.2. Базовые пути поиска модулей](#11-1-2-базовые-пути-поиска-модулей)
- [11.1.3. Поиск имен](#11-1-3-поиск-имен)
- [11.1.3.1. Кроссмодульная перегрузка функций](#11-1-3-1-кроссмодульная-перегрузка-функций)
- [11.1.4. Объявления public import](#11-1-4-объявления-public-import)
- [11.1.5. Объявления static import](#11-1-5-объявления-static-import)
- [11.1.6. Избирательные включения](#11-1-6-избирательные-включения)
- [11.1.7. Включения с переименованием](#11-1-7-включения-с-переименованием)
- [11.1.8. Объявление модуля](#11-1-8-объявление-модуля)
- [11.1.9. Резюме модулей](#11-1-9-резюме-модулей)
- [11.2. Безопасность](#11-2-безопасность)
- [11.2.1. Определенное и неопределенное поведение](#11-2-1-определенное-и-неопределенное-поведение)
- [11.2.2. Атрибуты @safe, @trusted и @system](#11-2-2-атрибуты-safe-trusted-и-system)
- [11.3. Конструкторы и деструкторы модулей](#11-3-конструкторы-и-деструкторы-модулей)
- [11.3.1. Порядок выполнения в рамках модуля](#11-3-1-порядок-выполнения-в-рамках-модуля)
- [11.3.2. Порядок выполнения при участии нескольких модулей](#11-3-2-порядок-выполнения-при-участии-нескольких-модулей)
- [11.4. Документирующие комментарии](#11-4-документирующие-комментарии)
- [11.5. Взаимодействие с C и C++](#11-5-взаимодействие-с-c-и-c)
- [11.5.1. Взаимодействие с классами C++](#11-5-1-взаимодействие-с-классами-c-6)
- [11.6. Ключевое слово deprecated](#11-6-ключевое-слово-deprecated)
- [11.7. Объявления версий](#11-7-объявления-версий)
- [11.8. Отладочные объявления](#11-8-отладочные-объявления)
- [11.9. Стандартная библиотека D](#11-9-стандартная-библиотека-d)
- [11.10. Встроенный ассемблер](#11-10-встроенный-ассемблер-8)
- [11.10.1. Архитектура x86](#11-10-1-архитектура-x86)
- [11.10.2. Архитектура x86-64](#11-10-2-архитектура-x86-64)
- [11.10.3. Разделение на версии](#11-10-3-разделение-на-версии)
- [11.10.4. Соглашения о вызовах](#11-10-4-соглашения-о-вызовах)
- [11.10.4.1. Соглашения о вызовах архитектуры x86](#11-10-4-1-соглашения-о-вызовах-архитектуры-x86)
- [11.10.4.2. Соглашения о вызовах архитектуры x86-64](#11-10-4-2-соглашения-о-вызовах-архитектуры-x86-64)
- [11.10.5. Рациональность](#11-10-5-рациональность)
Поговорка гласит, что программу в 100 строк можно заставить работать, даже если она нарушает все законы правильного кодирования. Эта поговорка расширяема: действительно, можно написать программу в 10 000 строк, уделяя внимание лишь деталям кода и не соблюдая никаких более масштабных правил надлежащей модульной разработки. Возможно, где-то есть и проекты в несколько миллионов строк, нарушающие немало правил крупномасштабной разработки.
@ -137,7 +140,7 @@ import goodies.io;
то компилятор сможет найти все артефакты стандартной библиотеки, не требуя никаких параметров в командной строке. Чтобы точно узнать, где ищется каждый из модулей, можно при запуске компилятора `dmd` добавить флаг `-v` (от verbose подробно). Подробное описание того, как установленная вами версия D загружает конфигурационные параметры, вы найдете в документации для нее (в случае `dmd` документация размещена в Интернете).
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-1-2-базовые-пути-поиска-модулей) [Наверх ⮍](#11-расширение-масштаба)
### 11.1.3. Поиск имен
@ -213,7 +216,7 @@ void main()
Обратите внимание: сама собой двусмысленность не проявляется. Если вы не попытаетесь обратиться к идентификатору в двусмысленной форме, компилятор никогда не пожалуется.
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-1-3-поиск-имен) [Наверх ⮍](#11-расширение-масштаба)
#### 11.1.3.1. Кроссмодульная перегрузка функций
@ -244,7 +247,7 @@ void main()
Добавив или удалив из инструкции `import` идентификатор `widget` или `acme.goodies.io`, можно заставить сломанную программу работать, или сломать работающую программу, или оставить работающую программу работающей но никогда с различными решениями относительно вызовов `fun` в последнем случае.
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-1-3-1-кроссмодульная-перегрузка-функций) [Наверх ⮍](#11-расширение-масштаба)
### 11.1.4. Объявления public import
@ -288,7 +291,7 @@ void main()
Наконец, в некотором более старом коде можно увидеть объявления `private import`. Такая форма использования допустима и аналогична обычному объявлению `import`.
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-1-4-объявления-public-import) [Наверх ⮍](#11-расширение-масштаба)
### 11.1.5. Объявления static import
@ -325,7 +328,7 @@ static
}
```
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-1-5-объявления-static-import) [Наверх ⮍](#11-расширение-масштаба)
### 11.1.6. Избирательные включения
@ -354,7 +357,7 @@ void main()
Высокая точность и контроль, предоставляемые избирательным включением, сделали это средство довольно популярным есть программисты, не приемлющие ничего, кроме избирательного включения; особенно много таких среди тех, кто прежде работал с языками, обладающими более слабыми механизмами включения и управления видимостью. И все же необходимо отметить, что другие упомянутые выше механизмы уничтожения двусмысленности, которые предоставляет D, ничуть не менее эффективны. Полный контроль над включаемыми идентификаторами был бы гораздо более полезен, если бы механизм поиска идентификаторов, используемый D по умолчанию, не был безошибочным.
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-1-6-избирательные-включения) [Наверх ⮍](#11-расширение-масштаба)
### 11.1.7. Включения с переименованием
@ -428,7 +431,7 @@ import std.stdio : say = writeln, CFile = File;
Дважды переименовывающее объявление `import` эквивалентно двум другим объявлениям. Первое из этих объявлений переименовывает только модуль, а второе только включаемый идентификатор. Таким образом, новая семантика определяется в терминах более простых, уже известных видов инструкции `import`. Предыдущее определение вводит идентификаторы `io.writeln`, `io.File`, `say` и `CFile`.
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-1-7-включения-с-переименованием) [Наверх ⮍](#11-расширение-масштаба)
### 11.1.8. Объявление модуля
@ -450,7 +453,7 @@ module path.to.nonexistent.location.app;
Тогда компилятор сгенерирует всю информацию о модуле, как будто он называется `app.d` и расположен в каталоге `path/to/nonexistent/location`. Компилятору все равно, потому что он не обращается по этому адресу: поиск файлов ассоциируется исключительно с `import`, а здесь, при непосредственной компиляции `gnome-cool-app.d`, никаких включений нет.
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-1-8-объявление-модуля) [Наверх ⮍](#11-расширение-масштаба)
### 11.1.9. Резюме модулей
@ -571,7 +574,7 @@ module acme.io.file;
Если библиотека acme широко используется, ее можно сделать одной из библиотек, которые проект использует по умолчанию. Но тут уже многое зависит от реализации компилятора и от операционной системы, так что для успеха операции придется прочесть это жуткое руководство.
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-1-9-резюме-модулей) [Наверх ⮍](#11-расширение-масштаба)
## 11.2. Безопасность
@ -602,7 +605,7 @@ void main()
Изменение одного из элементов массива `array` (какого именно, зависит от реализации компилятора, но обычно второго или третьего) изменяет `obj.x`.
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-2-безопасность) [Наверх ⮍](#11-расширение-масштаба)
### 11.2.1. Определенное и неопределенное поведение
@ -619,9 +622,9 @@ void main()
Программа безопасна, если она не порождает неопределенное поведение.
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-2-1-определенное-и-неопределенное-поведение) [Наверх ⮍](#11-расширение-масштаба)
### 11.3.2. Атрибуты @safe, @trusted и @system
### 11.2.2. Атрибуты @safe, @trusted и @system
Нехитрый способ гарантировать отсутствие недиагностированных ошибок просто запретить все небезопасные конструкции D, например особые случаи применения выражения `cast`. Однако это означало бы невозможность реализовать на D многие системы. Иногда бывает очень нужно переступить границы абстракции, например, рассматривать область памяти, имеющей некоторый тип, как область памяти с другим типом. Именно так поступают менеджер памяти и сборщик мусора. В задачи языка D всегда входила способность выразить логику такого программного обеспечения на системном уровне.
@ -670,7 +673,7 @@ void deallocate(void* p);
В момент написания этой книги SafeD находится в состоянии α-версии, так что порой небезопасные программы проходят компиляцию, а безопасные нет, но мы активно работаем над решением этой проблемы.
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-2-2-атрибуты-safe-trusted-и-system) [Наверх ⮍](#11-расширение-масштаба)
## 11.3. Конструкторы и деструкторы модулей
@ -718,7 +721,7 @@ static ~this()
Статические деструкторы выполняются после того, как выполнение `main` завершится каким угодно образом, будь то нормальный возврат или порождение исключения. Модули могут определять любое количество деструкторов модуля и свободно чередовать конструкторы и деструкторы модуля.
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-3-конструкторы-и-деструкторы-модулей) [Наверх ⮍](#11-расширение-масштаба)
### 11.3.1. Порядок выполнения в рамках модуля
@ -726,7 +729,7 @@ static ~this()
Если один из конструкторов модуля не сможет выполниться и породит исключение, то не будет выполнена и функция `main`. Выполняются лишь статические деструкторы, лексически расположенные *выше* отказавшего конструктора модуля. Если не сможет выполниться и породит исключение какой-либо деструктор модуля, остальные деструкторы выполнены не будут, а приложение прекратит свое выполнение, выведя сообщение об ошибке в стандартный поток.
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-3-1-порядок-выполнения-в-рамках-модуля) [Наверх ⮍](#11-расширение-масштаба)
### 11.3.2. Порядок выполнения при участии нескольких модулей
@ -740,7 +743,7 @@ static ~this()
Проверка на циклическую зависимость модулей в настоящий момент делается во время исполнения. Такие циклы можно отследить и во время компиляции или сборки, но это мало что дает: проблема проявляется в том, что программа отказывается загружаться, и можно предположить, что перед публикацией программа запускается хотя бы один раз. Тем не менее чем раньше обнаружена проблема, тем лучше, так что язык оставляет реализации возможность выявить это некорректное состояние и сообщить о нем.
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-3-2-порядок-выполнения-при-участии-нескольких-модулей) [Наверх ⮍](#11-расширение-масштаба)
## 11.4. Документирующие комментарии
@ -752,7 +755,7 @@ D определяет для документирующих комментар
Всеобъемлющее изучение системы трансляции документирующих комментариев не входит в задачу этой книги. Замечу только, что вам не помешает уделить этому больше внимания; документация многих проектов на D, а также веб-сайт эталонной реализации компилятора и его стандартной библиотеки полностью сгенерированы на основе документирующих комментариев D.
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-4-документирующие-комментарии) [Наверх ⮍](#11-расширение-масштаба)
## 11.5. Взаимодействие с C и C++
@ -783,7 +786,7 @@ extern(C++) double bar(double)
Компилятор опять организует необходимое декорирование имен и использует соглашение о вызовах, подходящее для языка-клиента. То есть эту функцию можно с одинаковым успехом вызывать из модулей как на D, так и на «иностранных языках».
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-5-взаимодействие-с-c-и-c) [Наверх ⮍](#11-расширение-масштаба)
### 11.5.1. Взаимодействие с классами C++[^6]
@ -857,7 +860,7 @@ void main()
}
```
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-5-1-взаимодействие-с-классами-c-6) [Наверх ⮍](#11-расширение-масштаба)
## 11.6. Ключевое слово deprecated
@ -865,7 +868,7 @@ void main()
Ключевое слово `deprecated` служит для планомерной постепенной миграции от старых версий API к более новым версиям. Причисляя соответствующие объявления к устаревшим, можно настроить компилятор так, чтобы он или принимал, или отклонял объявления с префиксом `deprecated`. Подготовив очередное изменение, отключите компиляцию `deprecated` ошибки точно укажут, где требуется ваше вмешательство, что позволит вам шаг за шагом обновить код.
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-6-ключевое-слово-deprecated) [Наверх ⮍](#11-расширение-масштаба)
## 11.7. Объявления версий
@ -920,7 +923,7 @@ version = ProEdition; // Ошибка!
Компиляторы, как водится, имеют множество предопределенных версий, таких как платформа (например, `Win32`, `Posix` или `Mac`), порядок байтов (`LittleEndian`, `BigEndian`) и так далее. Если включено тестирование модулей, автоматически задается проверка `version(unittest)`. Особыми идентификаторами времени исполнения `__FILE__` и `__LINE__` обозначаются соответственно имя текущего файла и строка в этом файле. Полный список определений `version` приведен в документации вашего компилятора.
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-7-объявления-версий) [Наверх ⮍](#11-расширение-масштаба)
## 11.8. Отладочные объявления
@ -942,7 +945,7 @@ void fun()
Чтобы отладить модуль `mymodule`, укажите в командной строке при компиляции этого модуля флаг `-debug=mymodule`, и выражение `debug(mymodule)` вернет `true`, что позволит скомпилировать код, «охраняемый» соответствующей конструкцией `debug`. Если использовать `debug(5)`, то «охраняемый» этой конструкцией код будет включен при уровне отладки `>= 5`. Уровень отладки устанавливается либо присваиванием `debug` целочисленной константы, либо флагом компиляции. Допустимо также использовать конструкцию `debug` без аргументов. Код, следующий за такой конструкцией, будет добавлен, если компиляция запущена с флагом `-debug`. Как и в случае `version`, нельзя присваивать отладочной версии идентификатор после того, как он уже был проверен.
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-8-отладочные-объявления) [Наверх ⮍](#11-расширение-масштаба)
## 11.9. Стандартная библиотека D
@ -981,7 +984,7 @@ void fun()
|`std.utf`|Функции для манипулирования кодировками UTF|
|`std.variant`|Объявление типа `Variant`, который является контейнером для хранения значения любого типа. `Variant` это высокоуровневый `union`|
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-9-стандартная-библиотека-d) [Наверх ⮍](#11-расширение-масштаба)
## 11.10. Встроенный ассемблер[^8]
@ -989,7 +992,7 @@ void fun()
К моменту написания данной книги компиляторы языка D существовали для платформ x86 и x86-64, соответственно синтаксис встроенного ассемблера определен пока только для этих платформ.
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-10-встроенный-ассемблер-8) [Наверх ⮍](#11-расширение-масштаба)
### 11.10.1. Архитектура x86
@ -1168,7 +1171,7 @@ fdiv ST,ST(1); // Правильно
fmul ST,ST(0); // Правильно
```
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-10-1-архитектура-x86) [Наверх ⮍](#11-расширение-масштаба)
### 11.10.2. Архитектура x86-64
@ -1199,7 +1202,7 @@ asm
К сожалению, выполнить переход по содержащемуся в `RIP` адресу с помощью `jmp`/`jxx` или `call` нельзя, равно как нельзя получить значение `RIP`, скопировав его в регистр общего назначения или стек. Впрочем, `call $;` как раз помещает в стек адрес следующей инструкции, что, по сути, идентично `push RIP;` (если бы такая инструкция была допустима). Подробную информацию можно найти в официальном руководстве по конкретному процессору.
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-10-2-архитектура-x86-64) [Наверх ⮍](#11-расширение-масштаба)
### 11.10.3. Разделение на версии
@ -1236,7 +1239,7 @@ void optimizedFunction(void* arg)
}
```
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-10-3-разделение-на-версии) [Наверх ⮍](#11-расширение-масштаба)
### 11.10.4. Соглашения о вызовах
@ -1257,7 +1260,7 @@ void optimizedFunction(void* arg)
То, как именно выполняются эти действия, определяется соглашениями о вызовах процедур. Их относительно немного, они хорошо стандартизированы. Разные языки используют разные соглашения о вызовах, но, как правило, допускают возможность использовать несколько соглашений. Соглашения о вызовах определяют, как передаются аргументы (через стек, через регистры, через общую память), порядок передачи аргументов, значение каких регистров следует сохранять, как передавать возвращаемое значение, кто возвращает указатель стека на исходную позицию (вызывающая или вызываемая процедура). В следующих разделах перечислены основные из этих соглашений.
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-10-4-соглашения-о-вызовах) [Наверх ⮍](#11-расширение-масштаба)
#### 11.10.4.1. Соглашения о вызовах архитектуры x86
@ -1315,7 +1318,7 @@ extern(C) int increment(int a) {
В остальных случаях аргументы передаются через скрытый аргумент, размещенный на стеке. В `EAX` в этом случае помещается указатель на этот аргумент.
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-10-4-1-соглашения-о-вызовах-архитектуры-x86) [Наверх ⮍](#11-расширение-масштаба)
#### 11.10.4.2. Соглашения о вызовах архитектуры x86-64
@ -1329,7 +1332,7 @@ extern(C) int increment(int a) {
Данное соглашение о вызовах используется в Posix-совместимых операционных системах и напоминает предыдущее, однако использует больше регистров. Для передачи целых чисел и адресов используются регистры `RDI`, `RSI`, `RDX`, `RCX`, `R8` и `R9`, для передачи чисел с плавающей запятой `XMM0`, `XMM1`, `XMM2`, `XMM3`, `XMM4`, `XMM5`, `XMM6` и `XMM7`. Если требуется передать аргумент больше 64 бит, но не больше 256 бит, он передается по частям через регистры общего назначения. В отличие от `Microsoft x64`, для переданных в регистрах аргументов место в стеке не резервируется. Возвращаемое значение передается так же, как и в `Microsoft x64`.
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-10-4-2-соглашения-о-вызовах-архитектуры-x86-64) [Наверх ⮍](#11-расширение-масштаба)
### 11.10.5. Рациональность
@ -1378,7 +1381,7 @@ void fastProcess()
И все это ради того, чтобы выжать из функции `fastProcess` максимум производительности! Тут-то и надо задаться вопросом: а в самом ли деле эта функция является краеугольным камнем вашей программы? Может быть, ваша программа недостаточно производительна из-за ошибки на этапе проектирования, и выбор другого решения позволит сэкономить секунды процессорного времени против долей миллисекунд, сэкономленных на оптимизации `fastProcess`? А может, время и, как следствие, деньги, которых требует написание ассемблерного кода, лучше направить на повышение производительности целевой машины? В любом случае задействовать встроенный ассемблер для повышения производительности нужно в последнюю очередь, когда остальные средства уже испробованы.
[В начало ⮍]() [Наверх ⮍](#11-расширение-масштаба)
[В начало ⮍](#11-10-5-рациональность) [Наверх ⮍](#11-расширение-масштаба)
[^1]: Прямой порядок байтов от старшего к младшему байту. *Прим. пер.*
[^2]: Обратный порядок байтов от младшего к старшему байту. *Прим. пер.*