|
@@ -82,7 +82,7 @@ import std.stdio; // Получить доступ к writeln и всему ос
|
|
|
import widget;
|
|
|
```
|
|
|
|
|
|
-![]()
|
|
|
+
|
|
|
|
|
|
***Рис. 11.1.*** *Пример структуры каталога*
|
|
|
|
|
@@ -119,7 +119,1661 @@ import goodies.io;
|
|
|
|
|
|
[В начало ⮍](#11-1-1-объявления-import) [Наверх ⮍](#11-расширение-масштаба)
|
|
|
|
|
|
+### 11.1.2. Базовые пути поиска модулей
|
|
|
+
|
|
|
+Анализируя объявление `import`, компилятор выполняет поиск не толь
|
|
|
+ко относительно текущего каталога, где происходит компиляция. Ина
|
|
|
+че невозможно было бы использовать ни одну из стандартных библио
|
|
|
+тек или других библиотек, развернутых за пределами каталога текуще
|
|
|
+го проекта. В конце концов мы постоянно включаем модули из пакета
|
|
|
+`std`, хотя в поле зрения наших проектов нет никакого подкаталога `std`.
|
|
|
+Как же работает этот механизм?
|
|
|
+
|
|
|
+Как и многие другие языки, D позволяет задать набор *базовых путей*
|
|
|
+(*roots*), откуда начинается поиск модулей. С помощью аргумента, пере
|
|
|
+даваемого компилятору из командной строки, к списку базовых путей
|
|
|
+поиска модулей можно добавить любое количество каталогов. Точный
|
|
|
+синтаксис этой операции зависит от компилятора; эталонный компи
|
|
|
+лятор `dmd` использует флаг командной строки `-I`, сразу за которым ука
|
|
|
+зывается путь, например `-Ic:\Programs\dmd\src\phobos` для Windows-вер
|
|
|
+сии и `-I/usr/local/src/phobos` для UNIX-версии. С помощью дополнитель
|
|
|
+ных флагов `-I` можно добавить любое количество путей в список путей
|
|
|
+поиска.
|
|
|
+
|
|
|
+Например, при анализе объявления `import path.to.file` сначала подката
|
|
|
+лог `path/to`[^5] ищется в текущем каталоге. Если такой подкаталог сущест
|
|
|
+вует, запрашивается файл `file.d`. Если файл найден, поиск завершает
|
|
|
+ся. В противном случае такой же поиск выполняется, начиная с каждо
|
|
|
+го из базовых путей, заданных с помощью флага `-I`. Поиск завершается
|
|
|
+при первом нахождении модуля; если же все каталоги пройдены безре
|
|
|
+зультатно, компиляция прерывается с ошибкой «модуль не найден».
|
|
|
+
|
|
|
+Если компонент `path.to` отсутствует, поиск модуля будет осуществлять
|
|
|
+ся непосредственно в базовых каталогах.
|
|
|
+
|
|
|
+Для пользователя было бы обременительно добавлять флаг командной
|
|
|
+строки только для того, чтобы получить доступ к стандартной библио
|
|
|
+теке или другим широко используемым библиотекам. Вот почему эта
|
|
|
+лонный компилятор (и фактически любой другой) использует простой
|
|
|
+конфигурационный файл, содержащий несколько флагов командной
|
|
|
+строки по умолчанию, которые автоматически добавляются к каждому
|
|
|
+вызову компилятора из командной строки. Сразу после инсталляции
|
|
|
+компилятора конфигурационный файл должен содержать такие уста
|
|
|
+новки, чтобы с его помощью можно было найти, по крайней мере, биб
|
|
|
+лиотеку поддержки времени исполнения и стандартную библиотеку.
|
|
|
+Поэтому если вы просто введете
|
|
|
+
|
|
|
+```sh
|
|
|
+% dmd main.d
|
|
|
+```
|
|
|
+
|
|
|
+то компилятор сможет найти все артефакты стандартной библиотеки,
|
|
|
+не требуя никаких параметров в командной строке. Чтобы точно узнать,
|
|
|
+где ищется каждый из модулей, можно при запуске компилятора `dmd`
|
|
|
+добавить флаг `-v` (от verbose – подробно). Подробное описание того, как
|
|
|
+установленная вами версия D загружает конфигурационные парамет
|
|
|
+ры, вы найдете в документации для нее (в случае `dmd` документация раз
|
|
|
+мещена в Интернете).
|
|
|
+
|
|
|
+### 11.1.3. Поиск имен
|
|
|
+
|
|
|
+Как ни странно, в D нет глобального контекста или глобального про
|
|
|
+странства имен. В частности, нет способа определить истинно глобаль
|
|
|
+ный объект, функцию или имя класса. Причина в том, что единствен
|
|
|
+ный способ определить такую сущность – разместить ее в модуле, а у лю
|
|
|
+бого модуля должно быть имя. В свою очередь, имя модуля порождает
|
|
|
+именованный контекст. Даже `Object`, предок всех классов, в действи
|
|
|
+тельности не является глобальным именем: на самом деле, это объект
|
|
|
+`object.Object`, поскольку он вводится в модуле `object`, поставляемом по
|
|
|
+умолчанию. Вот, например, содержимое файла `widget.d`:
|
|
|
+
|
|
|
+```d
|
|
|
+// Содержимое файла widget.d
|
|
|
+void fun(int x)
|
|
|
+{
|
|
|
+ ...
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+С определением функции `fun` не вводится глобально доступный иденти
|
|
|
+фикатор `fun`. Вместо этого все, кто включает модуль `widget` (например,
|
|
|
+файл `main.d`), получают доступ к идентификатору `widget.fun`:
|
|
|
+
|
|
|
+```d
|
|
|
+// Содержимое main.d
|
|
|
+import widget;
|
|
|
+
|
|
|
+void main()
|
|
|
+{
|
|
|
+ widget.fun(10); // Все в порядке, ищем функцию fun в модуле widget
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Все это очень хорошо и модульно, но при этом довольно многословно
|
|
|
+и неоправданно строго. Если нужна функция `fun` и никто больше ее не
|
|
|
+определяет, почему компилятор не может просто отдать предпочтение
|
|
|
+`widget.fun` как единственному претенденту?
|
|
|
+
|
|
|
+На самом деле, именно так и работает поиск имен. Каждый включае
|
|
|
+мый модуль вносит свое пространство имен, но когда требуется найти
|
|
|
+идентификатор, предпринимаются следующие шаги:
|
|
|
+
|
|
|
+1. Идентификатор ищется в текущем контексте. Если идентификатор
|
|
|
+найден, поиск успешно завершается.
|
|
|
+2. Идентификатор ищется в контексте текущего модуля. Если иденти
|
|
|
+фикатор найден, поиск успешно завершается.
|
|
|
+3. Идентификатор ищется во всех включенных модулях:
|
|
|
+ - если идентификатор не удается найти, поиск завершается неуда
|
|
|
+чей;
|
|
|
+ - если идентификатор найден в единственном модуле, поиск ус
|
|
|
+пешно завершается;
|
|
|
+ - если идентификатор найден более чем в одном модуле и этот иден
|
|
|
+тификатор не является именем функции, поиск завершается
|
|
|
+с ошибкой, выводится сообщение о дублировании идентифика
|
|
|
+тора;
|
|
|
+ - если идентификатор найден более чем в одном модуле и этот иден
|
|
|
+тификатор является именем функции, применяется механизм раз
|
|
|
+решения имен при кроссмодульной перегрузке (см. раздел 5.5.2).
|
|
|
+
|
|
|
+Привлекательным следствием такого подхода является то, что клиент
|
|
|
+ский код обычно может быть кратким, а многословным только тогда,
|
|
|
+когда это действительно необходимо. В предыдущем примере функция
|
|
|
+`main.d` могла вызвать `fun` проще, без каких-либо «украшений»:
|
|
|
+
|
|
|
+```d
|
|
|
+// Содержимое main.d
|
|
|
+import widget;
|
|
|
+
|
|
|
+void main()
|
|
|
+{
|
|
|
+ fun(10); // Все в порядке, идентификатор fun определен только в модуле widget
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Пусть в файле `io.d` также определена функция `fun` с похожей сигнату
|
|
|
+рой:
|
|
|
+
|
|
|
+```d
|
|
|
+// Содержимое io.d из каталога acme/goodies
|
|
|
+void fun(long n)
|
|
|
+{
|
|
|
+ ...
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+И пусть модуль с функцией `main` включает и файл `widget.d`, и файл `io.d`.
|
|
|
+Тогда «неприукрашенный» вызов `fun` окажется ошибочным, но уточ
|
|
|
+ненные вызовы с указанием имени модуля по-прежнему будут работать
|
|
|
+нормально:
|
|
|
+
|
|
|
+```d
|
|
|
+// Содержимое main.d
|
|
|
+import widget, acme.goodies.io;
|
|
|
+
|
|
|
+void main()
|
|
|
+{
|
|
|
+ fun(10); // Ошибка! Двусмысленный вызов функции fun(): идентификатор fun найден в модулях widget и acme.goodies.io
|
|
|
+ widget.fun(10); // Все в порядке, точное указание
|
|
|
+ acme.goodies.io.fun(10); // Все в порядке, точное указание
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Обратите внимание: сама собой двусмысленность не проявляется. Если
|
|
|
+вы не попытаетесь обратиться к идентификатору в двусмысленной фор
|
|
|
+ме, компилятор никогда не пожалуется.
|
|
|
+
|
|
|
+#### 11.1.3.1. Кроссмодульная перегрузка функций
|
|
|
+
|
|
|
+В разделе 5.5.2 обсуждается вопрос перегрузки функций в случае их
|
|
|
+расположения в разных модулях и приводится пример, в котором моду
|
|
|
+ли, определяющие функцию с одним и тем же именем, вовсе не
|
|
|
+обязательно порождают двусмысленность. Теперь, когда мы уже знаем
|
|
|
+больше о модулях и модульности, пора поставить точку в этом разговоре.
|
|
|
+
|
|
|
+*Угон функций* (*function hijacking*) представляет собой особенно хитрое
|
|
|
+нарушение модульности. Угон функций имеет место, когда функция
|
|
|
+в некотором модуле состязается за вызовы из функции в другом модуле
|
|
|
+и принимает их на себя. Типичное проявление угона функций: работаю
|
|
|
+щий модуль ведет себя по-разному в зависимости от того, каковы другие
|
|
|
+включенные модули, или от порядка, в котором эти модули включены.
|
|
|
+
|
|
|
+Угоны могут появляться как следствие непредвиденных эффектов в дру
|
|
|
+гих случаях исправно выполняемых и благонамеренных правил. В ча
|
|
|
+стности, кажется логичным, чтобы в предыдущем примере, где модуль
|
|
|
+`widget` определяет `fun(int)`, а модуль `acme.goodies.io` – `fun(long)`, вызов
|
|
|
+`fun(10)`, сделанный в `main`, был присужден функции `widget.fun`, посколь
|
|
|
+ку это «лучший» вариант. Однако это один из тех случаев, когда луч
|
|
|
+шее – враг хорошего. Если модуль с функцией `main` включает только
|
|
|
+`acme.goodies.io`, то вызов `fun(10)`, естественно, отдается `acme.goodies.io.fun`
|
|
|
+как единственному кандидату. Однако если на сцену выйдет модуль
|
|
|
+`widget`, вызов `fun(10)` неожиданно переходит к `widget.fun`. На самом деле,
|
|
|
+`widget` вмешивается в контракт, который изначально заключался меж
|
|
|
+ду `main` и `acme.goodies.io` – ужасное нарушение модульности.
|
|
|
+
|
|
|
+Неудивительно, что языки программирования остерегаются угона. C++
|
|
|
+разрешает угон функций, но большинство руководств по стилю про
|
|
|
+граммирования советуют этого приема избегать; а Python, как и мно
|
|
|
+гие другие языки, и вовсе запрещает любой угон. С другой стороны, пе
|
|
|
+реизбыток воздержания может привести к излишне строгим правилам,
|
|
|
+воспитывающим привычку использовать в именах длинные строки
|
|
|
+идентификаторов.
|
|
|
+
|
|
|
+D разрешает проблему угона оригинальным способом. Основной руково
|
|
|
+дящий принцип подхода D к кроссмодульной перегрузке состоит в том,
|
|
|
+что добавление или уничтожение включаемых модулей не должно вли
|
|
|
+ять на разрешение имени функции. Возня с инструкциями `import` мо
|
|
|
+жет привести к тому, что ранее компилируемые модули перестанут ком
|
|
|
+пилироваться, а ранее некомпилируемые модули станут компилируе
|
|
|
+мыми. Опасный сценарий, который D исключает, – тот, при котором,
|
|
|
+поиграв с объявлениями `import`, вы оставите программу компилируе
|
|
|
+мой, но с разными результатами разрешения имен при перегрузке.
|
|
|
+
|
|
|
+Для любого вызова функции, найденного в модуле, справедливо, что
|
|
|
+если имя этой функции найдено более чем в одном модуле *и* если вызов
|
|
|
+может сработать с версией функции из любого модуля, то такой вызов
|
|
|
+ошибочен. Если же вызов можно заставить работать лишь при одном ва
|
|
|
+рианте разрешения имени, такой вызов легален, поскольку при таких
|
|
|
+условиях нет угрозы угона.
|
|
|
+
|
|
|
+В приведенном примере, где `widget` определяет `fun(int)`, а `acme.goodies.io` –
|
|
|
+`fun(long)`, положение дел в модуле `main` таково:
|
|
|
+
|
|
|
+```d
|
|
|
+import widget, acme.goodies.io;
|
|
|
+
|
|
|
+void main()
|
|
|
+{
|
|
|
+ fun(10); // Ошибка! Двусмысленная кроссмодульная перегрузка!
|
|
|
+ fun(10L); // Все в порядке, вызов недвусмысленно переходит к acme.goodies.io.fun
|
|
|
+ fun("10"); // Ошибка! Ничего не подходит!
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Добавив или удалив из инструкции `import` идентификатор `widget` или
|
|
|
+`acme.goodies.io`, можно заставить сломанную программу работать, или
|
|
|
+сломать работающую программу, или оставить работающую програм
|
|
|
+му работающей – но никогда с различными решениями относительно
|
|
|
+вызовов `fun` в последнем случае.
|
|
|
+
|
|
|
+### 11.1.4. Объявления public import
|
|
|
+
|
|
|
+По умолчанию поиск идентификаторов во включаемых модулях не яв
|
|
|
+ляется транзитивным. Рассмотрим каталог на рис. 11.1. Если модуль
|
|
|
+main включает модуль widget, а модуль widget в свою очередь включает
|
|
|
+модуль acme.gadget, то поиск идентификатора, начатый из main, в модуле
|
|
|
+acme.gadget производиться не будет. Какие бы модули ни включал мо
|
|
|
+дуль widget, это лишь деталь реализации модуля widget, и для main она
|
|
|
+не имеет значения.
|
|
|
+Тем не менее может статься, что модуль widget окажется лишь расшире
|
|
|
+нием другого модуля или будет иметь смысл лишь в связке с другим мо
|
|
|
+дулем. Например, определения из модуля widget могут использовать
|
|
|
+и требовать так много определений из модуля acme.goodies.io, что для
|
|
|
+любого другого модуля было бы бесполезно использовать widget, не
|
|
|
+включив также и acme.goodies.io. В таких случаях вы можете помочь
|
|
|
+клиентскому коду, воспользовавшись объявлением public import:
|
|
|
+// Содержимое widget.d
|
|
|
+// Сделать идентификаторы из acme.goodies.io видимыми всем клиентам widget
|
|
|
+public import acme.goodies.io;
|
|
|
+Данное объявление public import делает все идентификаторы, опреде
|
|
|
+ленные модулем acme/goodies/io.d, видимыми из модулей, включающих
|
|
|
+widget.d (внимание) как будто widget.d определил их сам. По сути, public
|
|
|
+import добавляет в widget.d объявление alias для каждого идентифика
|
|
|
+тора из io.d. (Дублирование кода объектов не происходит, только неко
|
|
|
+торое дублирование идентификаторов.) Предположим, что модуль io.d
|
|
|
+определяет функцию print(string), а в функцию main.d поместим сле
|
|
|
+дующий код:
|
|
|
+import widget;
|
|
|
+void main() {
|
|
|
+print("Здравствуй");
|
|
|
+// Все в порядке, идентификатор print найден
|
|
|
+widget.print("Здравствуй"); // Все в порядке, widget фактически
|
|
|
+// определяет print
|
|
|
+}
|
|
|
+Что если на самом деле включить в main и модуль acme.goodies.io? По
|
|
|
+пробуем это сделать:
|
|
|
+import widget;
|
|
|
+import acme.goodies.io; // Излишне, но безвредно
|
|
|
+void main() {
|
|
|
+print("Здравствуй");
|
|
|
+// Все в порядке...
|
|
|
+widget.print("Здравствуй");
|
|
|
+// ...в порядке...
|
|
|
+acme.goodies.io.print("Здравствуй"); // ... и в порядке!
|
|
|
+}
|
|
|
+Модулю io.d вред не нанесен: тот факт, что модуль widget определяет
|
|
|
+псевдоним для acme.goodies.io, ни в коей мере не влияет на исходный
|
|
|
+идентификатор. Дополнительный псевдоним – это просто альтернатив
|
|
|
+ное средство получения доступа к одному и тому же определению.
|
|
|
+Наконец, в некотором более старом коде можно увидеть объявления
|
|
|
+private import. Такая форма использования допустима и аналогична
|
|
|
+обычному объявлению import.
|
|
|
+
|
|
|
+11.1.5. Объявления static import
|
|
|
+Иногда добавление включаемого модуля в неявный список для поиска
|
|
|
+идентификаторов при объявлении import (в соответствии с алгоритмом
|
|
|
+из раздела 11.1.3) может быть нежелательным. Бывает уместным жела
|
|
|
+ние осуществлять доступ к определенному в модуле функционалу толь
|
|
|
+ко с явным указанием полного имени (а-ля имямодуля.имяидентификатора,
|
|
|
+а не имяидентификатора).
|
|
|
+Простейший случай, когда такое решение оправданно, – использование
|
|
|
+очень популярного модуля в связке с модулем узкого назначения при
|
|
|
+совпадении ряда идентификаторов в этих модулях. Например, в стан
|
|
|
+дартном модуле std.string определены широко используемые функции
|
|
|
+для обработки строк. Если вы взаимодействуете с устаревшей систе
|
|
|
+мой, применяющей другую кодировку (например, двухбайтный набор
|
|
|
+знаков, известный как DBCS – Double Byte Character Set), то захотите
|
|
|
+использовать идентификаторы из std.string в большинстве случаев,
|
|
|
+а идентификаторы из собственного модуля dbcs_string – лишь изредка
|
|
|
+и с точным указанием. Для этого нужно просто указать в объявлении
|
|
|
+import для dbcs_string ключевое слово static:
|
|
|
+import std.string;
|
|
|
+// Определяет функцию string toupper(string)
|
|
|
+static import dbcs_string; // Тоже определяет функцию string toupper(string)
|
|
|
+void main() {
|
|
|
+auto s1 = toupper("hello");
|
|
|
+// Все в порядке
|
|
|
+auto s2 = dbcs_string.toupper("hello"); // Все в порядке
|
|
|
+}
|
|
|
+Уточним: если бы этот код не включал объявление import std.string,
|
|
|
+первый вызов просто не компилировался бы. Для static import поиск
|
|
|
+идентификаторов не автоматизируется, даже когда идентификатор не
|
|
|
+двусмысленно разрешается.
|
|
|
+Бывают и другие ситуации, когда конструкция static import может быть
|
|
|
+полезной. Сдержать автоматический поиск и использовать более много
|
|
|
+словный, но одновременно и более точный подход может пожелать и мо
|
|
|
+дуль, включающий множество других модулей. В таких случаях клю
|
|
|
+чевое слово static полезно использовать с целыми списками значений,
|
|
|
+разделенных запятыми:
|
|
|
+static import teleport, time_travel, warp;
|
|
|
+Или располагать его перед контекстом, заключенным в скобки, с тем
|
|
|
+же результатом:
|
|
|
+static {
|
|
|
+import teleport;
|
|
|
+import time_travel, warp;
|
|
|
+}
|
|
|
+
|
|
|
+11.1.6. Избирательные включения
|
|
|
+Другой эффективный способ справиться с конфликтующими иденти
|
|
|
+фикаторами – включить лишь определенные идентификаторы из моду
|
|
|
+ля. Для этого используйте следующий синтаксис:
|
|
|
+// Содержимое main.d
|
|
|
+import widget : fun, gun;
|
|
|
+Избирательные включения обладают точностью хирургического лазе
|
|
|
+ра: данное объявление import вводит ровно два идентификатора – fun
|
|
|
+и gun. После избирательного включения невидим даже идентификатор
|
|
|
+widget! Предположим, модуль widget определяет идентификаторы fun,
|
|
|
+gun и hun. В таком случае fun и gun можно будет использовать только так,
|
|
|
+будто их определил сам модуль main. Любые другие попытки, такие как
|
|
|
+hun, widget.hun и даже widget.fun, незаконны:
|
|
|
+// Содержимое main.d
|
|
|
+import widget : fun, gun;
|
|
|
+void main() {
|
|
|
+fun();
|
|
|
+// Все в порядке
|
|
|
+gun();
|
|
|
+// Все в порядке
|
|
|
+hun();
|
|
|
+// Ошибка!
|
|
|
+widget.fun(); // Ошибка!
|
|
|
+widget.hun(); // Ошибка!
|
|
|
+}
|
|
|
+Высокая точность и контроль, предоставляемые избирательным вклю
|
|
|
+чением, сделали это средство довольно популярным – есть программи
|
|
|
+сты, не приемлющие ничего, кроме избирательного включения; особен
|
|
|
+но много таких среди тех, кто прежде работал с языками, обладающи
|
|
|
+ми более слабыми механизмами включения и управления видимостью.
|
|
|
+И все же необходимо отметить, что другие упомянутые выше механиз
|
|
|
+мы уничтожения двусмысленности, которые предоставляет D, ничуть
|
|
|
+не менее эффективны. Полный контроль над включаемыми идентифи
|
|
|
+каторами был бы гораздо более полезен, если бы механизм поиска иден
|
|
|
+тификаторов, используемый D по умолчанию, не был безошибочным.
|
|
|
+
|
|
|
+11.1.7. Включения с переименованием
|
|
|
+Большие проекты имеют тенденцию создавать запутанные иерархии
|
|
|
+пакетов. Чрезмерно ветвистые структуры каталогов – довольно частый
|
|
|
+артефакт разработки, особенно в проектах, где заранее вводят щедрую,
|
|
|
+всеобъемлющую схему именования, способную сохранить стабильность
|
|
|
+даже при непредвиденных добавлениях в проект. Вот почему нередки
|
|
|
+ситуации, когда модулю приходится использовать очень глубоко вло
|
|
|
+женный модуль:
|
|
|
+import util.container.finite.linear.list;
|
|
|
+В таких случаях может быть весьма полезно включение с переименова
|
|
|
+нием, позволяющее присвоить сущности util.container.finite.linear.list
|
|
|
+короткое имя:
|
|
|
+import list = util.container.finite.linear.list;
|
|
|
+С таким объявлением import программа может использовать идентифи
|
|
|
+катор list.symbol вместо чересчур длинного идентификатора util.con
|
|
|
+tainer.finite.linear.list.symbol. Если исходить из того, что модуль, о ко
|
|
|
+тором идет речь, определяет класс List, в итоге получим:
|
|
|
+import list = util.container.finite.linear.list;
|
|
|
+void main() {
|
|
|
+auto lst1 = new list.List;
|
|
|
+// Все в порядке
|
|
|
+auto lst2 = new util.container.finite.linear.list.List; // Ошибка!
|
|
|
+// Идентификатор util не определен!
|
|
|
+auto lst3 = new List;
|
|
|
+// Ошибка!
|
|
|
+// Идентификатор List не определен!
|
|
|
+}
|
|
|
+Включение с переименованием не делает видимыми переименованные
|
|
|
+пакеты (то есть util, container, …, list), так что попытка использовать ис
|
|
|
+ходное длинное имя в определении lst2 завершается неудачей при поис
|
|
|
+ке первого же идентификатора util. Кроме того, включение с переимено
|
|
|
+ванием, без сомнения, обладает статической природой (см. раздел 11.1.5)
|
|
|
+в том смысле, что не использует механизм автоматического поиска; вот
|
|
|
+почему не вычисляется выражение new List. Если вы действительно хо
|
|
|
+тите не только переименовать идентификаторы, но еще и сделать их ви
|
|
|
+димыми, очень удобно использовать конструкцию alias (см. раздел 7.4):
|
|
|
+import util.container.finite.linear.list;
|
|
|
+// Нестатическое включение
|
|
|
+alias util.container.finite.linear.list list; // Для удобства
|
|
|
+void main() {
|
|
|
+auto lst1 = new list.List;
|
|
|
+// Все в порядке
|
|
|
+auto lst2 = new util.container.finite.linear.list.List; // Все в порядке
|
|
|
+auto lst3 = new List;
|
|
|
+// Все в порядке
|
|
|
+}
|
|
|
+Переименование также может использоваться в связке с избирательны
|
|
|
+ми включениями (см. раздел 11.1.6). Продемонстрируем это на примере:
|
|
|
+import std.stdio : say = writeln;
|
|
|
+void main() {
|
|
|
+say("Здравствуй, мир!");
|
|
|
+// Все в порядке, вызвать writeln
|
|
|
+std.stdio.say("Здравствуй, мир");
|
|
|
+// Ошибка!
|
|
|
+writeln("Здравствуй, мир!");
|
|
|
+// Ошибка!
|
|
|
+std.stdio.writeln("Здравствуй, мир!"); // Ошибка!
|
|
|
+}
|
|
|
+Как и ожидалось, применив избирательное включение, которое одно
|
|
|
+временно еще и переименовывает идентификатор, вы делаете видимым
|
|
|
+лишь включаемый идентификатор и ничего больше.
|
|
|
+Наконец, можно переименовать и модуль, и включаемый идентифика
|
|
|
+тор (включаемые идентификаторы):
|
|
|
+import io = std.stdio : say = writeln, CFile = File;
|
|
|
+Возможные взаимодействия между двумя переименованными включен
|
|
|
+ными идентификаторами могли бы вызвать некоторые противоречия.
|
|
|
+Язык D решил этот вопрос, просто сделав предыдущее объявление тож
|
|
|
+дественным следующим:
|
|
|
+import io = std.stdio : writeln, File;
|
|
|
+import std.stdio : say = writeln, CFile = File;
|
|
|
+Дважды переименовывающее объявление import эквивалентно двум дру
|
|
|
+гим объявлениям. Первое из этих объявлений переименовывает только
|
|
|
+модуль, а второе – только включаемый идентификатор. Таким образом,
|
|
|
+новая семантика определяется в терминах более простых, уже извест
|
|
|
+ных видов инструкции import. Предыдущее определение вводит иден
|
|
|
+тификаторы io.writeln, io.File, say и CFile.
|
|
|
+
|
|
|
+11.1.8. Объявление модуля
|
|
|
+Как говорилось в разделе 11.1, по той простой причине, что import при
|
|
|
+нимает лишь идентификаторы, пакеты и модули на D, которые предпо
|
|
|
+лагается хоть когда-либо включать в другие модули с помощью этой
|
|
|
+конструкции, должны иметь имена, являющиеся допустимыми иден
|
|
|
+тификаторами языка D.
|
|
|
+В отдельных ситуациях требуется, чтобы модуль замаскировался име
|
|
|
+нем, отличным от имени файла, где расположен код модуля, и притво
|
|
|
+рился бы, что путь до пакета, которому принадлежит модуль, отлича
|
|
|
+ется от пути до каталога, где на самом деле располагается упомянутый
|
|
|
+файл. Очевидная ситуация, когда это может понадобиться: имя модуля
|
|
|
+не является допустимым идентификатором D.
|
|
|
+Предположим, вы пишете программу, которая придерживается более
|
|
|
+широкого соглашения по именованию, предписывающего использовать
|
|
|
+дефисы в имени файла, например gnome-cool-app.d. Тогда компилятор D
|
|
|
+откажется компилировать ее, даже если сама программа будет полно
|
|
|
+стью корректной. И все потому, что во время компиляции D должен ге
|
|
|
+нерировать информацию о каждом модуле, каждый модуль должен об
|
|
|
+ладать допустимым именем, а gnome-cool-app не является таковым. Про
|
|
|
+стой способ обойти это правило – хранить исходный код под именем
|
|
|
+gnome-cool-app, а на этапе сборки переименовывать его, например в gnome_
|
|
|
+cool_app.d. Этот трюк, конечно, сработает, но есть способ проще и луч
|
|
|
+ше: достаточно вставить в начало файла объявление модуля, которое
|
|
|
+выглядит так:
|
|
|
+module gnome_cool_app;
|
|
|
+Если такое объявление присутствует в gnome-cool-app.d (но обязательно
|
|
|
+в качестве первого объявления в файле), то компилятор будет доволен,
|
|
|
+поскольку он генерирует всю информацию о модуле, используя имя
|
|
|
+gnome_cool_app. В таком случае истинное имя вообще никак не проверя
|
|
|
+ется; в объявлении модуля имя может быть хоть таким:
|
|
|
+module path.to.nonexistent.location.app;
|
|
|
+Тогда компилятор сгенерирует всю информацию о модуле, как будто он
|
|
|
+называется app.d и расположен в каталоге path/to/nonexistent/location.
|
|
|
+Компилятору все равно, потому что он не обращается по этому адресу:
|
|
|
+поиск файлов ассоциируется исключительно с import, а здесь, при непо
|
|
|
+средственной компиляции gnome-cool-app.d, никаких включений нет.
|
|
|
+
|
|
|
+11.1.9. Резюме модулей
|
|
|
+Язык D поощряет модель разработки, которая не требует отделения объ
|
|
|
+явлений от сущностей, определяемых программой (в C и C++ эти поня
|
|
|
+тия фигурируют как «заголовки» и «исходные коды»). Вы просто рас
|
|
|
+полагаете код в модуле и включаете этот модуль в другие с помощью
|
|
|
+конструкции import. Тем не менее иногда хочется принять другую мо
|
|
|
+дель разработки, предписывающую более жесткое разделение между
|
|
|
+сигнатурами, которые модуль должен реализовать, и кодом, который
|
|
|
+стоит за этими сигнатурами. В этом случае потребуется работать с так
|
|
|
+называемыми резюме модулей (module summaries), построенными на
|
|
|
+основе исходного кода. Резюме модуля – это минимум того, что необхо
|
|
|
+димо знать модулю о другом модуле, чтобы использовать его.
|
|
|
+Резюме модуля – это фактически модуль без комментариев и реализа
|
|
|
+ций функций. Реализации функций, использующих параметры време
|
|
|
+ни компиляции, тем не менее в резюме модуля остаются. Ведь функции
|
|
|
+с параметрами времени компиляции должны быть доступны во время
|
|
|
+компиляции, так как могут быть вызваны непредвиденным образом
|
|
|
+в модуле-клиенте.
|
|
|
+Резюме модуля состоит из корректного кода на D. Например:
|
|
|
+/**
|
|
|
+Это документирующий комментарий для этого модуля
|
|
|
+*/
|
|
|
+module acme.doitall;
|
|
|
+/**
|
|
|
+Это документирующий комментарий для класса A
|
|
|
+*/
|
|
|
+class A {
|
|
|
+void fun() { ... }
|
|
|
+final void gun() { ... }
|
|
|
+}
|
|
|
+class B(T) {
|
|
|
+void hun() { ... }
|
|
|
+}
|
|
|
+void foo() {
|
|
|
+...
|
|
|
+}
|
|
|
+void bar(int n)(float x) {
|
|
|
+...
|
|
|
+}
|
|
|
+При составлении резюме модуля doitall этот модуль копируется, но ис
|
|
|
+ключаются все комментарии, а тела всех функций заменяются на ; (ис
|
|
|
+ключение составляют функции с параметрами времени компиляции –
|
|
|
+такие функции остаются нетронутыми):
|
|
|
+module acme.doitall;
|
|
|
+class A {
|
|
|
+void fun();
|
|
|
+final void gun();
|
|
|
+}
|
|
|
+class B(T) {
|
|
|
+void hun() { ... }
|
|
|
+}
|
|
|
+void foo();
|
|
|
+void bar(int n)(float x) {
|
|
|
+...
|
|
|
+}
|
|
|
+Резюме содержит информацию, необходимую другому модулю, чтобы
|
|
|
+использовать acme.doitall. В большинстве случаев резюме модулей ав
|
|
|
+томатически вычисляются внутри работающего компилятора. Но ком
|
|
|
+пилятор может сгенерировать резюме по исходному коду и по вашему
|
|
|
+запросу (в случае эталонной реализации компилятора dmd для этого пред
|
|
|
+назначен флаг -H). Сгенерированные резюме полезны, когда вы, к приме
|
|
|
+ру, хотите распространить библиотеку в виде заголовков плюс скомпи
|
|
|
+лированная библиотека.
|
|
|
+Заметим, что исключение тел функций все же не гарантировано. Ком
|
|
|
+пилятор волен оставлять тела очень коротких функций в целях инлай
|
|
|
+нинга. Например, если функция acme.doitall.foo обладает пустым те
|
|
|
+лом или просто вызывает другую функцию, ее тело может присутство
|
|
|
+вать в сгенерированном интерфейсном файле.
|
|
|
+Подход к разработке, хорошо знакомый программистам на C и C++, за
|
|
|
+ключается в сопровождении заголовочных файлов (то есть резюме)
|
|
|
+и файлов с реализациями вручную и по отдельности. Если вы изберете
|
|
|
+этот способ, работать придется несколько больше, но зато вы сможете
|
|
|
+поупражняться в коллективном руководстве. Например, право изме
|
|
|
+нять заголовочные файлы может быть закреплено за командой проек
|
|
|
+тировщиков, контролирующей все детали интерфейсов, которые моду
|
|
|
+ли предоставляют друг другу. А команду программистов, реализующих
|
|
|
+эти интерфейсы, можно наделить правом изменять файлы реализации
|
|
|
+и правом на чтение (но не изменение) заголовочных файлов, используе
|
|
|
+мых в качестве текущей документации, направляющей процесс реали
|
|
|
+зации. Компилятор проверяет, соответствует ли реализация интерфей
|
|
|
+су (ну, по крайней мере синтаксически).
|
|
|
+С языком D у вас есть выбор – вы можете: 1) вообще обойтись без резюме
|
|
|
+модулей, 2) разрешить компилятору сгенерировать их за вас, 3) сопро
|
|
|
+вождать модули и резюме модулей вручную. Все примеры в этой книге
|
|
|
+выбирают вариант 1) – не использовать резюме модулей, оставив все
|
|
|
+заботы компилятору. Чтобы опробовать две другие возможности, вам
|
|
|
+сначала потребуется организовать модули так, чтобы их иерархия соот
|
|
|
+ветствовала изображенной на рис. 11.2.
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+Рис. 11.2. Структура каталога для отделения резюме модулей
|
|
|
+(«заголовков») от файлов реализации
|
|
|
+
|
|
|
+Чтобы использовать пакет acme, потребуется добавить родительский ка
|
|
|
+талог каталогов acme и acme_impl к базовым путям поиска модулей про
|
|
|
+екта (см. раздел 11.1.2), а затем включить модули из acme в клиентский
|
|
|
+код с помощью следующих объявлений:
|
|
|
+// Из модуля client.d
|
|
|
+import acme.algebra;
|
|
|
+import acme.io.network;
|
|
|
+Каталог acme включает только файлы резюме. Чтобы заставить файлы
|
|
|
+реализации взаимодействовать, необходимо, чтобы в качестве префик
|
|
|
+са в именах соответствующих модулей фигурировал пакет acme, а не
|
|
|
+acme_impl. Вот где приходят на помощь объявления модулей. Даже не
|
|
|
+смотря на то, что файл algebra.d находится в каталоге acme_impl, вклю
|
|
|
+чив следующее объявление, модуль algebra может заявить, что входит
|
|
|
+в пакет acme:
|
|
|
+// Из модуля acme_impl/algebra.d
|
|
|
+module acme.algebra;
|
|
|
+Соответственно модули в подпакете io будут использовать объявление:
|
|
|
+// Из модуля acme_impl/io/file.d
|
|
|
+module acme.io.file;
|
|
|
+Эти строки позволят компилятору сгенерировать должные имена паке
|
|
|
+тов и модулей. Чтобы во время сборки программы компилятор нашел
|
|
|
+тела функций, просто передайте ему файлы реализации:
|
|
|
+% dmd client.d /path/to/acme_impl/algebra.d
|
|
|
+Директива import в client.d обнаружит интерфейсный файл acme.di в ка
|
|
|
+талоге /path/to/acme. А компилятор найдет файл реализации точно там,
|
|
|
+где указано в командной строке, с корректными именами пакета и мо
|
|
|
+дуля.
|
|
|
+Если коду из client.d потребуется использовать множество модулей из
|
|
|
+пакета acme, станет неудобно указывать все эти модули в командной
|
|
|
+строке компилятора. В таких случаях лучший вариант – упаковать весь
|
|
|
+код пакета acme в бинарную библиотеку и передавать dmd только ее. Син
|
|
|
+таксис для сборки библиотеки зависит от реализации компилятора; ес
|
|
|
+ли вы работаете с эталонной реализацией, вам потребуется сделать что-
|
|
|
+то типа этого:
|
|
|
+% cd /path/to/acme_impl
|
|
|
+% dmd -lib -ofacme algebra.d gui.d io/file.d io/network.d
|
|
|
+Флаг -lib предписывает компилятору собрать библиотеку, а флаг -of (от
|
|
|
+output file – файл вывода) направляет вывод в файл acme.lib (Windows)
|
|
|
+или acme.a (UNIX-подобные системы). Чтобы клиентский код мог рабо
|
|
|
+тать с такой библиотекой, нужно ввести что-то вроде:
|
|
|
+% dmd client.d acme.lib
|
|
|
+Если библиотека acme широко используется, ее можно сделать одной из
|
|
|
+библиотек, которые проект использует по умолчанию. Но тут уже мно
|
|
|
+гое зависит от реализации компилятора и от операционной системы,
|
|
|
+так что для успеха операции придется прочесть это жуткое руководство.
|
|
|
+
|
|
|
+11.2. Безопасность
|
|
|
+Понятие безопасности языков программирования всегда было противо
|
|
|
+речивым, но за последние годы его определение удивительно кристал
|
|
|
+лизовалось.
|
|
|
+Интуитивно понятно, что безопасный язык тот, который «защищает
|
|
|
+свои собственные абстракции» [46, гл. 1]. В качестве примера таких аб
|
|
|
+стракций D приведем класс:
|
|
|
+class A { int x; }
|
|
|
+и массив:
|
|
|
+float[] array;
|
|
|
+По правилам языка D (тоже «абстракция», предоставляемая языком)
|
|
|
+изменение внутреннего элемента x любого объекта типа A не должно из
|
|
|
+менять какой-либо элемент массива array, и наоборот, изменение array[n]
|
|
|
+для некоторого n не должно изменять элемент x некоторого объекта ти
|
|
|
+па A. Как ни благоразумно запрещать такие бессмысленные операции,
|
|
|
+в D есть способы заставить их обе выполниться – формируя указатели
|
|
|
+с помощью cast или задействуя union.
|
|
|
+void main() {
|
|
|
+float[] array = new float[1024];
|
|
|
+auto obj = cast(A) array.ptr;
|
|
|
+...
|
|
|
+}
|
|
|
+Изменение одного из элементов массива array (какого именно, зависит
|
|
|
+от реализации компилятора, но обычно второго или третьего) изменяет
|
|
|
+obj.x.
|
|
|
+
|
|
|
+11.2.1. Определенное и неопределенное поведение
|
|
|
+Кроме только что приведенного примера с сомнительным приведением
|
|
|
+указателя на float к ссылке на класс есть и другие ошибки времени ис
|
|
|
+полнения, свидетельствующие о том, что язык нарушил определенные
|
|
|
+обещания. Хорошими примерами могут послужить разыменование ука
|
|
|
+зателя null, деление на ноль, а также извлечение вещественного квад
|
|
|
+ратного корня из отрицательного числа. Никакая корректная програм
|
|
|
+ма не должна когда-либо выполнять такие операции, и тот факт, что
|
|
|
+они все же могут иметь место в программе, типы которой проверяются,
|
|
|
+можно рассматривать как несостоятельность системы типов.
|
|
|
+Проблема подобного критерия корректности, который «хорошо было
|
|
|
+бы принять»: список ошибок бесконечно пополняется. D сводит свое по
|
|
|
+нятие безопасности к очень точному и полезному определению: безопас
|
|
|
+ная программа на D характеризуется только определенным поведени
|
|
|
+ем. Различия между определенным и неопределенным поведением:
|
|
|
+• определенное поведение: выполнение фрагмента программы в задан
|
|
|
+ном состоянии завершается одним из заранее определенных исхо
|
|
|
+дов; один из возможных исходов – резкое прекращение выполнения
|
|
|
+(именно это происходит при разыменовании указателя null и при де
|
|
|
+лении на ноль);
|
|
|
+• неопределенное поведение: эффект от выполнения фрагмента про
|
|
|
+граммы в заданном состоянии не определен. Это означает, что может
|
|
|
+произойти все, что угодно в пределах физических возможностей.
|
|
|
+Хороший пример – только что упомянутый случай с cast: програм
|
|
|
+ма с такой «раковой клеткой» некоторое время может продолжать
|
|
|
+работу, но наступит момент, когда какая-нибудь запись в array с по
|
|
|
+следующим случайным обращением к obj приведет к тому, что ис
|
|
|
+полнение выйдет из-под контроля.
|
|
|
+(Неопределенное поведение перекликается с понятием недиагностиро
|
|
|
+ванных ошибок, введенным Карделли [15]. Он выделяет две большие
|
|
|
+категории ошибок времени исполнения: диагностированные и недиаг
|
|
|
+ностированные ошибки. Диагностированные ошибки вызывают немед
|
|
|
+ленный останов исполнения, а недиагностированные – выполнение про
|
|
|
+извольных команд. В программе с определенным поведением никогда
|
|
|
+не возникнет недиагностированная ошибка.)
|
|
|
+У противопоставления определенного поведения неопределенному есть
|
|
|
+пара интересных нюансов. Рассмотрим, к примеру, язык, определяю
|
|
|
+щий операцию деления на ноль с аргументами типа int, так что она
|
|
|
+должна всегда порождать значение int.max. Такое условие переводит де
|
|
|
+ление на ноль в разряд определенного поведения – хотя данное опреде
|
|
|
+ление этого действия и нельзя назвать полезным. Примерно в том же
|
|
|
+ключе std.math в действительности определяет, что операция sqrt(-1)
|
|
|
+должна возвращать double.nan. Это также определенное поведение, по
|
|
|
+скольку double.nan – вполне определенное значение, которое является
|
|
|
+частью спецификации языка, а также функции sqrt. Даже деление на
|
|
|
+ноль – не ошибка для типов с плавающей запятой: этой операции забот
|
|
|
+ливо предписывается возвращать или плюс бесконечность, или минус
|
|
|
+бесконечность, или NaN («нечисло») (см. главу 2). Результаты выполне
|
|
|
+ния программ всегда будут предсказуемыми, когда речь идет о функ
|
|
|
+ции sqrt или делении чисел с плавающей запятой.
|
|
|
+Программа безопасна, если она не порождает неопределенное поведение.
|
|
|
+
|
|
|
+11.3.2. Атрибуты @safe, @trusted и @system
|
|
|
+Нехитрый способ гарантировать отсутствие недиагностированных оши
|
|
|
+бок – просто запретить все небезопасные конструкции D, например осо
|
|
|
+бые случаи применения выражения cast. Однако это означало бы невоз-
|
|
|
+можность реализовать на D многие системы. Иногда бывает очень нуж
|
|
|
+но переступить границы абстракции, например, рассматривать область
|
|
|
+памяти, имеющей некоторый тип, как область памяти с другим типом.
|
|
|
+Именно так поступают менеджер памяти и сборщик мусора. В задачи
|
|
|
+языка D всегда входила способность выразить логику такого программ
|
|
|
+ного обеспечения на системном уровне.
|
|
|
+С другой стороны, многие приложения нуждаются в небезопасном до
|
|
|
+ступе к памяти лишь в сильно инкапсулированной форме. Язык может
|
|
|
+заявить о том, что он безопасен, даже если его сборщик мусора реализо
|
|
|
+ван на небезопасном языке. Ведь с точки зрения безопасного языка нет
|
|
|
+возможности использовать сборщик небезопасным образом. Сборщик
|
|
|
+сам по себе инкапсулирован внутри библиотеки поддержки времени ис
|
|
|
+полнения, реализован на другом языке и воспринимается безопасным
|
|
|
+языком как волшебный примитив. Любой недостаток безопасности
|
|
|
+сборщика мусора был бы проблемой реализации языка, а не клиентско
|
|
|
+го кода.
|
|
|
+Как может большой проект обеспечить безопасность большинства своих
|
|
|
+модулей, в то же время обходя правила в некоторых избранных случаях?
|
|
|
+Подход D к безопасности – предоставить пользователю право самому
|
|
|
+решать, чего он хочет: вы можете на уровне объявлений заявить, при
|
|
|
+держивается ли ваш код правил безопасности или ему нужна возмож
|
|
|
+ность переступить ее границы. Обычно информация о свойствах моду
|
|
|
+ля указывается сразу же после объявления модуля, как здесь:
|
|
|
+module my_widget;
|
|
|
+@safe:
|
|
|
+...
|
|
|
+В этом месте определяются атрибуты @safe, @trusted и @system, которые
|
|
|
+позволяют модулю объявить о своем уровне безопасности. (Такой под
|
|
|
+ход не нов; в языке Модула-3 применяется тот же подход, чтобы отли-
|
|
|
+чить небезопасные и безопасные модули.)
|
|
|
+Код, размещенный после атрибута @safe, обязуется использовать ин
|
|
|
+струкции лишь из безопасного подмножества D, что означает:
|
|
|
+• никаких преобразований указателей в неуказатели (например, int),
|
|
|
+и наоборот;
|
|
|
+• никаких преобразований между указателями, типы которых не име
|
|
|
+ют отношения друг к другу;
|
|
|
+• проверка границ при любом обращении к массиву;
|
|
|
+• никаких объединений, включающих указатели, классы и массивы,
|
|
|
+а также структуры, которые содержат перечисленные запрещенные
|
|
|
+типы в качестве внутренних элементов;
|
|
|
+• никаких арифметических операций с указателями;
|
|
|
+• запрет на получение адреса локальной переменной (на самом деле,
|
|
|
+требуется запрет утечки таких адресов, но отследить это гораздо
|
|
|
+сложнее);
|
|
|
+• функции должны вызывать лишь функции, обладающие атрибутом
|
|
|
+@safe или @trusted;
|
|
|
+• никаких ассемблерных вставок;
|
|
|
+• никаких преобразований типа, лишающих данные статуса const,
|
|
|
+immutable или shared;
|
|
|
+• никаких обращений к каким-либо сущностям с атрибутом @system.
|
|
|
+Иногда эти правила могут оказаться излишне строгими; например,
|
|
|
+в стремлении избежать утечки указателей на локальные переменные
|
|
|
+можно исключить из рядов безопасных программ очевидно корректные
|
|
|
+программы. Тем не менее безопасное подмножество D (по прозвищу
|
|
|
+SafeD) все же довольно мощное – целые приложения могут быть полно
|
|
|
+стью написаны на SafeD.
|
|
|
+Объявление или группа объявлений могут заявить, что им, напротив,
|
|
|
+требуется низкоуровневый доступ. Такие объявления должны содер
|
|
|
+жать атрибут @system:
|
|
|
+@system:
|
|
|
+void * allocate(size_t size);
|
|
|
+void deallocate(void* p);
|
|
|
+...
|
|
|
+Атрибут @system действенно отключает все проверки, позволяя исполь
|
|
|
+зовать необузданную мощь языка – на счастье или на беду.
|
|
|
+Наконец, подход библиотек нередко состоит в том, что они предлагают
|
|
|
+клиентам безопасные абстракции, подспудно используя небезопасные
|
|
|
+средства. Такой подход применяют многие компоненты стандартной
|
|
|
+библиотеки D. В таких объявлениях можно указывать атрибут @trusted.
|
|
|
+Модулям без какого-либо атрибута доступен уровень безопасности, на
|
|
|
+значаемый по умолчанию. Выбор уровня по умолчанию можно настро
|
|
|
+ить с помощью конфигурационных файлов компилятора и флагов ко
|
|
|
+мандной строки; точная настройка зависит от реализации компилятора.
|
|
|
+Эталонная реализация компилятора dmd предлагает атрибут по умолча
|
|
|
+нию @system; задать атрибут по умолчанию @safe можно с помощью фла
|
|
|
+га командной строки -safe.
|
|
|
+В момент написания этой книги SafeD находится в состоянии α-версии,
|
|
|
+так что порой небезопасные программы проходят компиляцию, а без
|
|
|
+опасные – нет, но мы активно работаем над решением этой проблемы.
|
|
|
+
|
|
|
+11.3. Конструкторы и деструкторы модулей
|
|
|
+Иногда модулям требуется выполнить какой-то инициализирующий
|
|
|
+код для вычисления некоторых статических данных. Сделать это мож
|
|
|
+но, вставляя явные проверки («Были ли эти данные добавлены?») везде,
|
|
|
+где осуществляется доступ к соответствующим данным. Если такой
|
|
|
+подход неудобен/неэффективен, помогут конструкторы модулей.
|
|
|
+Предположим, что вы пишете модуль, зависящий от операционной сис
|
|
|
+темы, и поведение этого модуля зависит от флага. Во время компиляции
|
|
|
+легко распознать основные платформы (например, «Я Mac» или «Я PC»),
|
|
|
+но определять версию Windows придется во время исполнения.
|
|
|
+Чтобы немного упростить задачу, условимся, что наш код различает
|
|
|
+лишь ОС Windows Vista и более поздние или ранние версии относитель
|
|
|
+но нее. Пример кода, определяющего вид операционной системы на эта
|
|
|
+пе инициализации модуля:
|
|
|
+private enum WinVersion { preVista, vista, postVista }
|
|
|
+private WinVersion winVersion;
|
|
|
+static this() {
|
|
|
+OSVERSIONINFOEX info;
|
|
|
+info.dwOSVersionInfoSize = OSVERSIONINFOEX.sizeof;
|
|
|
+GetVersionEx(&info) || assert(false);
|
|
|
+if (info.dwMajorVersion < 6) {
|
|
|
+winVersion = WinVersion.preVista;
|
|
|
+} else if (info.dwMajorVersion == 6 && info.dwMinorVersion == 0) {
|
|
|
+winVersion = WinVersion.vista;
|
|
|
+} else {
|
|
|
+winVersion = WinVersion.postVista;
|
|
|
+}
|
|
|
+}
|
|
|
+Этот геройский подвиг совершает конструктор модуля static this(). Та
|
|
|
+кие конструкторы модулей всегда выполняются до main. Любой задан
|
|
|
+ный модуль может содержать любое количество конструкторов.
|
|
|
+В свою очередь, синтаксис деструкторов модулей предсказуем:
|
|
|
+// На уровне модуля
|
|
|
+static ~this() {
|
|
|
+...
|
|
|
+}
|
|
|
+Статические деструкторы выполняются после того, как выполнение main
|
|
|
+завершится каким угодно образом, будь то нормальный возврат или по
|
|
|
+рождение исключения. Модули могут определять любое количество де
|
|
|
+структоров модуля и свободно чередовать конструкторы и деструкторы
|
|
|
+модуля.
|
|
|
+
|
|
|
+11.3.1. Порядок выполнения в рамках модуля
|
|
|
+Порядок выполнения конструкторов модуля в рамках заданного моду
|
|
|
+ля всегда соответствует последовательности расположения этих кон
|
|
|
+структоров в модуле, то есть сверху вниз (лексический порядок). Поря
|
|
|
+док выполнения деструкторов модуля – снизу вверх (обратный лексиче
|
|
|
+ский порядок).
|
|
|
+Если один из конструкторов модуля не сможет выполниться и породит
|
|
|
+исключение, то не будет выполнена и функция main. Выполняются лишь
|
|
|
+статические деструкторы, лексически расположенные выше отказавше
|
|
|
+го конструктора модуля. Если не сможет выполниться и породит ис
|
|
|
+ключение какой-либо деструктор модуля, остальные деструкторы вы
|
|
|
+полнены не будут, а приложение прекратит свое выполнение, выведя
|
|
|
+сообщение об ошибке в стандартный поток.
|
|
|
+
|
|
|
+11.3.2. Порядок выполнения
|
|
|
+при участии нескольких модулей
|
|
|
+Если модулей несколько, определить порядок вызовов сложнее. Эти пра
|
|
|
+вила идентичны определенным для статических конструкторов классов
|
|
|
+(см. раздел 6.3.6) и исходят из того, что модули, включаемые другими
|
|
|
+модулями, должны инициализироваться первыми, а очищаться – по
|
|
|
+следними. Вот правила, определяющие порядок выполнения статиче
|
|
|
+ских конструкторов модулей модуль1 и модуль2:
|
|
|
+•
|
|
|
+•
|
|
|
+•
|
|
|
+•
|
|
|
+•
|
|
|
+конструкторы или деструкторы модулей определяются только в од
|
|
|
+ном из модулей модуль1 и модуль2, тогда не нужно заботиться об упо
|
|
|
+рядочивании;
|
|
|
+модуль1 не включает модуль модуль2, а модуль2 не включает модуль1:
|
|
|
+упорядочивание не регламентируется – любой порядок сработает,
|
|
|
+поскольку модули не зависят друг от друга;
|
|
|
+модуль1 включает модуль2: конструкторы модуля2 выполняются до кон
|
|
|
+структоров модуля1, а деструкторы модуля2 – после деструкторов моду
|
|
|
+ля1;
|
|
|
+модуль2 включает модуль1: конструкторы модуля1 выполняются до
|
|
|
+конструкторов модуля2, а деструкторы модуля1 – после деструкторов
|
|
|
+модуля2;
|
|
|
+модуль1 включает модуль2, а модуль2 вкючает модуль1: диагностируется
|
|
|
+ошибка «циклическая зависимость» и выполнение прерывается на
|
|
|
+этапе загрузки программы.
|
|
|
+Проверка на циклическую зависимость модулей в настоящий момент
|
|
|
+делается во время исполнения. Такие циклы можно отследить и во вре
|
|
|
+мя компиляции или сборки, но это мало что дает: проблема проявляет
|
|
|
+ся в том, что программа отказывается загружаться, и можно предполо
|
|
|
+жить, что перед публикацией программа запускается хотя бы один раз.
|
|
|
+Тем не менее чем раньше обнаружена проблема, тем лучше, так что
|
|
|
+язык оставляет реализации возможность выявить это некорректное
|
|
|
+состояние и сообщить о нем.
|
|
|
+
|
|
|
+11.4. Документирующие комментарии
|
|
|
+Писать документацию скучно, а для программиста нет ничего страш
|
|
|
+нее скуки. В результате документация обычно содержит скупые, непол
|
|
|
+ные и устаревшие сведения.
|
|
|
+Автоматизированные построители документации стараются вывести
|
|
|
+максимум информации из чистого кода, отразив заслуживающие вни
|
|
|
+мания отношения между сущностями. Тем не менее современным авто
|
|
|
+матизированным построителям нелегко задокументировать высоко
|
|
|
+уровневые намерения по реализации. Современные языки помогают им
|
|
|
+в этом, предписывая использовать так называемые документирующие
|
|
|
+комментарии – особые комментарии, описывающие, например, опре
|
|
|
+деленную пользователем сущность. Языковой процессор (или сам ком
|
|
|
+пилятор, или отдельная программа) просматривает комментарии вме
|
|
|
+сте с кодом и генерирует документацию в одном из популярных форма
|
|
|
+тов (таком как XML, HTML или PDF).
|
|
|
+D определяет для документирующих комментариев спецификацию,
|
|
|
+описывающую формат комментариев и процесс их преобразования в це
|
|
|
+левой формат. Сам процесс не зависит от целевого формата; транслятор,
|
|
|
+управляемый простым и гибким шаблоном (также определяемым поль
|
|
|
+зователем), генерирует документацию фактически в любом заданном
|
|
|
+формате.
|
|
|
+Всеобъемлющее изучение системы трансляции документирующих ком
|
|
|
+ментариев не входит в задачу этой книги. Замечу только, что вам не по
|
|
|
+мешает уделить этому больше внимания; документация многих проек
|
|
|
+тов на D, а также веб-сайт эталонной реализации компилятора и его
|
|
|
+стандартной библиотеки полностью сгенерированы на основе докумен
|
|
|
+тирующих комментариев D.
|
|
|
+
|
|
|
+11.5. Взаимодействие с C и C++
|
|
|
+Модули на D могут напрямую взаимодействовать с функциями C и C++.
|
|
|
+Есть ограничение: к этим функциям не относятся обобщенные функ
|
|
|
+ции С++, поскольку для этого компилятор D должен был бы включать
|
|
|
+полноценный компилятор C++. Кроме того, схема расположения полей
|
|
|
+класса D не совместима с классами C++, использующими виртуальное
|
|
|
+наследование.
|
|
|
+Чтобы вызвать функцию C или C++, просто укажите в объявлении функ
|
|
|
+ции язык и не забудьте связать ваш модуль с соответствующими биб
|
|
|
+лиотеками:
|
|
|
+extern(C) int foo(char*);
|
|
|
+extern(C++) double bar(double);
|
|
|
+Эти объявления сигнализируют компилятору, что вызов генерируется
|
|
|
+с соответствующими схемой расположения в стеке, соглашением о вы
|
|
|
+зовах и кодировкой имен (также называемой декорированием имен –
|
|
|
+name mangling), даже если сами функции D отличаются по всем или
|
|
|
+некоторым из этих пунктов.
|
|
|
+Чтобы вызвать функцию на D из программы на C или C++, просто до
|
|
|
+бавьте в реализацию одно из приведенных выше объявлений:
|
|
|
+extern(C) int foo(char*) {
|
|
|
+... // Реализация
|
|
|
+}
|
|
|
+extern(C++) double bar(double) {
|
|
|
+... // Реализация
|
|
|
+}
|
|
|
+Компилятор опять организует необходимое декорирование имен и ис
|
|
|
+пользует соглашение о вызовах, подходящее для языка-клиента. То
|
|
|
+есть эту функцию можно с одинаковым успехом вызывать из модулей
|
|
|
+как на D, так и на «иностранных языках».
|
|
|
+
|
|
|
+11.5.1. Взаимодействие с классами C++[^6]
|
|
|
+Как уже говорилось, D не способен отобразить классы C++ в классы D.
|
|
|
+Это связано с различием реализаций механизма наследования в этих
|
|
|
+языках. Тем не менее интерфейсы D очень похожи на классы C++, по
|
|
|
+этому D реализует следующий механизм взаимодействия с классами
|
|
|
+C++:
|
|
|
+// Код на С++
|
|
|
+class Foo {
|
|
|
+public:
|
|
|
+virtual int method(int a, int b) {
|
|
|
+return a + b;
|
|
|
+}
|
|
|
+};
|
|
|
+Foo* newFoo() {
|
|
|
+return new Foo();
|
|
|
+}
|
|
|
+void deleteFoo(Foo* obj) {
|
|
|
+delete obj;
|
|
|
+}
|
|
|
+// Код на D
|
|
|
+extern (C++) {
|
|
|
+interface Foo {
|
|
|
+int method(int, int);
|
|
|
+}
|
|
|
+Foo newFoo();
|
|
|
+void deleteFoo(Foo);
|
|
|
+}
|
|
|
+void main() {
|
|
|
+auto obj = newFoo;
|
|
|
+scope(exit) deleteFoo(obj);
|
|
|
+assert(obj.method(2, 3) == 5);
|
|
|
+}
|
|
|
+Следующий код создает класс, реализующий интерфейс С++, и исполь
|
|
|
+зует объект этого интерфейса в вызове внешней функции С++, прини
|
|
|
+мающей в качестве аргумента указатель на объект класса С++ Foo.
|
|
|
+
|
|
|
+extern (C++) void call(Foo);
|
|
|
+// В коде C++ эта функция должна быть определена как void call(Foo* f);
|
|
|
+extern (C++) interface Foo {
|
|
|
+int bar(int, int);
|
|
|
+}
|
|
|
+class FooImpl : Foo {
|
|
|
+extern (C++) int bar(int a, int b) {
|
|
|
+// ...
|
|
|
+}
|
|
|
+}
|
|
|
+void main() {
|
|
|
+FooImpl f = new FooImpl();
|
|
|
+call(f);
|
|
|
+}
|
|
|
+
|
|
|
+11.6. Ключевое слово deprecated
|
|
|
+Перед любым объявлением (типа, функции или данных) может распо
|
|
|
+лагаться ключевое слово deprecated. Оно действует как класс памяти,
|
|
|
+но нисколько не влияет собственно на генерацию кода. Вместо этого
|
|
|
+deprecated лишь информирует компилятор о том, что помеченная им
|
|
|
+сущность не предназначена для использования. Если такая сущность
|
|
|
+все же будет использована, компилятор выведет предупреждение или
|
|
|
+даже откажется компилировать, если он был запущен с соответствую
|
|
|
+щим флагом (-w в случае dmd).
|
|
|
+Ключевое слово deprecated служит для планомерной постепенной ми
|
|
|
+грации от старых версий API к более новым версиям. Причисляя соот
|
|
|
+ветствующие объявления к устаревшим, можно настроить компилятор
|
|
|
+так, чтобы он или принимал, или отклонял объявления с префиксом
|
|
|
+deprecated. Подготовив очередное изменение, отключите компиляцию
|
|
|
+deprecated – ошибки точно укажут, где требуется ваше вмешательство,
|
|
|
+что позволит вам шаг за шагом обновить код.
|
|
|
+
|
|
|
+11.7. Объявления версий
|
|
|
+В идеальном мире, как только программа написана, ее можно запус
|
|
|
+кать где угодно. А здесь, на Земле, то и дело что-то заставляет вносить
|
|
|
+в программу изменения – другая версия библиотеки, сборка для особых
|
|
|
+целей или зависимость от платформы. Чтобы помочь справиться с этим,
|
|
|
+D определяет объявление версии version, позволяющее компилировать
|
|
|
+код в зависимости от определенных условий.
|
|
|
+Способ использования версии намеренно прост и прямолинеен. Вы или
|
|
|
+устанавливаете версию, или проверяете ее. Сама версия может быть
|
|
|
+или целочисленной константой, или идентификатором:
|
|
|
+version = 20100501;
|
|
|
+version = FinalRelease;
|
|
|
+Чтобы проверить версию, напишите:
|
|
|
+version(20100501) {
|
|
|
+... // Объявления
|
|
|
+}
|
|
|
+version (PreFinalRelease) {
|
|
|
+... // Объявления
|
|
|
+} else version (FinalRelease) {
|
|
|
+... // Другие объявления
|
|
|
+} else {
|
|
|
+... // Еще объявления
|
|
|
+}
|
|
|
+Если версия уже присвоена, «охраняемые» проверкой объявления ком
|
|
|
+пилируются, иначе они игнорируются. Конструкция version может
|
|
|
+включать блок else, назначение которого очевидно.
|
|
|
+Установить версию можно лишь до того, как она будет прочитана. По
|
|
|
+пытки установить версию после того, как она была задействована в про
|
|
|
+верке, вызывают ошибку времени компиляции:
|
|
|
+version (ProEdition) {
|
|
|
+... // Объявления
|
|
|
+}
|
|
|
+version = ProEdition; // Ошибка!
|
|
|
+Реакция такова, поскольку присваивания версий не предназначены
|
|
|
+для того, чтобы версии изменять: версия должна быть одной и той же
|
|
|
+независимо от того, на какой фрагмент программы вы смотрите.
|
|
|
+Указывать версию можно не только в файлах с исходным кодом, но
|
|
|
+и в командной строке компилятора (например, -version=123 или -versi
|
|
|
+on=xyz в случае эталонной реализации компилятора dmd). Попытка уста
|
|
|
+новить версию как в командной строке, так и в файле с исходным кодом
|
|
|
+также приведет к ошибке.
|
|
|
+Простота семантики version не случайна. Было бы легко сделать кон
|
|
|
+струкцию version более мощной во многих отношениях, но очень скоро
|
|
|
+она начала бы работать наперекор своему предназначению. Например,
|
|
|
+управление версиями C с помощью связки директив #if/#elif/#else, без
|
|
|
+условно, позволяет реализовать больше тактик в определении версий –
|
|
|
+именно поэтому управление версиями в проекте на C обычно содержит
|
|
|
+змеиный клубок условий, направляющих компиляцию. Конструкция
|
|
|
+version языка D намеренно ограничена, чтобы с ее помощью можно бы
|
|
|
+ло реализовать лишь простое, единообразное управление версиями.
|
|
|
+Компиляторы, как водится, имеют множество предопределенных вер
|
|
|
+сий, таких как платформа (например, Win32, Posix или Mac), порядок бай
|
|
|
+тов (LittleEndian, BigEndian) и так далее. Если включено тестирование
|
|
|
+модулей, автоматически задается проверка version(unittest). Особыми
|
|
|
+идентификаторами времени исполнения __FILE__ и __LINE__ обознача
|
|
|
+ются соответственно имя текущего файла и строка в этом файле. Пол
|
|
|
+ный список определений version приведен в документации вашего ком
|
|
|
+пилятора.
|
|
|
+
|
|
|
+11.8. Отладочные объявления
|
|
|
+Отладочное объявление – это лишь особая версия с идентичным син
|
|
|
+таксисом присваивания и проверки. Конструкция debug была определе
|
|
|
+на специально для того, чтобы стандартизировать порядок объявления
|
|
|
+отладочных режимов и средств.
|
|
|
+Типичный случай использования конструкции debug:
|
|
|
+module mymodule;
|
|
|
+...
|
|
|
+void fun() {
|
|
|
+int x;
|
|
|
+...
|
|
|
+debug(mymodule) writeln("x=", x);
|
|
|
+...
|
|
|
+}
|
|
|
+Чтобы отладить модуль mymodule, укажите в командной строке при ком
|
|
|
+пиляции этого модуля флаг -debug=mymodule, и выражение debug(mymodule)
|
|
|
+вернет true, что позволит скомпилировать код, «охраняемый» соответ
|
|
|
+ствующей конструкцией debug. Если использовать debug(5), то «охраняе
|
|
|
+мый» этой конструкцией код будет включен при уровне отладки >= 5.
|
|
|
+Уровень отладки устанавливается либо присваиванием debug целочис
|
|
|
+ленной константы, либо флагом компиляции. Допустимо также ис
|
|
|
+пользовать конструкцию debug без аргументов. Код, следующий за та
|
|
|
+кой конструкцией, будет добавлен, если компиляция запущена с фла
|
|
|
+гом -debug. Как и в случае version, нельзя присваивать отладочной вер
|
|
|
+сии идентификатор после того, как он уже был проверен.
|
|
|
+
|
|
|
+11.9. Стандартная библиотека D
|
|
|
+Стандартная библиотека D, фигурирующая в коде под именем Phobos[^7],
|
|
|
+органично развивалась вместе с языком. В результате она включает как
|
|
|
+API старого стиля, так и новейшие библиотечные артефакты, исполь
|
|
|
+зующие более современные средства языка.
|
|
|
+Библиотека состоит из двух основных пакетов – core и std. Первый со
|
|
|
+держит фундаментальные средства поддержки времени исполнения:
|
|
|
+реализации встроенных типов, сборщик мусора, код для начала и завер
|
|
|
+шения работы, поддержка многопоточности, определения, необходимые
|
|
|
+для доступа к библиотеке времени исполнения языка C, и другие компо
|
|
|
+ненты, связанные с перечисленными. Пакет std предоставляет функ
|
|
|
+циональность более высокого уровня. Преимущество такого подхода
|
|
|
+в том, что другие библиотеки можно надстраивать поверх core, а с паке
|
|
|
+том std они будут лишь сосуществовать, не требуя его присутствия.
|
|
|
+Пакет std обладает плоской структурой: большинство модулей распо
|
|
|
+лагаются в корне пакета. Каждый модуль посвящен отдельной функ
|
|
|
+циональной области. Информация о некоторых наиболее важных моду
|
|
|
+лях библиотеки Phobos представлена в табл. 11.2.
|
|
|
+
|
|
|
+Таблица 11.2. Обзор стандартных модулей
|
|
|
+
|
|
|
+|Модуль|Описание|
|
|
|
+|-|-|
|
|
|
+|`std.algorithm`|Этот модуль можно считать основой мощнейшей способности
|
|
|
+к обобщению, присущей языку. Вдохновлен стандартной биб
|
|
|
+лиотекой шаблонов C++ (Standard Template Library, STL). Со
|
|
|
+держит больше 70 важных алгоритмов, реализованных очень
|
|
|
+обобщенно. Большинство алгоритмов применяются к струк
|
|
|
+турированным последовательностям идентичных элементов.
|
|
|
+В STL базовой абстракцией последовательности служит ите
|
|
|
+ратор, соответствующий примитив D – диапазон, для которо
|
|
|
+го краткого обзора явно недостаточно; полное введение в диа
|
|
|
+пазоны D доступно в Интернете|
|
|
|
+|`std.array`|Функции для удобства работы с массивами|
|
|
|
+|`std.bigint`|Целое число переменной длины с сильно оптимизированной
|
|
|
+реализацией|
|
|
|
+|`std.bitmanip`|Типы и часто используемые функции для низкоуровневых би
|
|
|
+товых операций|
|
|
|
+|`std.concurrency`|Средства параллельных вычислений (см. главу 13)|
|
|
|
+|`std.container`|Реализации разнообразных контейнеров|
|
|
|
+|`std.conv`|Универсальный магазин, удовлетворяющий любые нужды по
|
|
|
+преобразованиям. Здесь определены многие полезные функ
|
|
|
+ции, такие как to и text|
|
|
|
+|`std.datetime`|Полезные вещи, связанные с датой и временем|
|
|
|
+|`std.file`|Файловые утилит ы. Зачаст ую этот мод уль манип улируе т
|
|
|
+файлами целиком; например, в нем есть функция read, кото
|
|
|
+рая считывает весь файл, при этом std.file.read и понятия не
|
|
|
+имеет о том, что можно открывать файл и читать его малень
|
|
|
+кими порциями (об этом заботится модуль std.stdio, см. далее)|
|
|
|
+|`std.functional`|Примитивы для определения и композиции функций|
|
|
|
+|`std.getopt`|Синтаксический анализ командной строки|
|
|
|
+|`std.json`|Обработка данных в формате JSON|
|
|
|
+|`std.math`|В высшей степени оптимизированные, часто используемые
|
|
|
+математические функции|
|
|
|
+|`std.numeric`|Общие числовые алгоритмы|
|
|
|
+|`std.path`|Утилиты для манипуляций с путями к файлам|
|
|
|
+|`std.random`|Разнообразные генераторы случайных чисел|
|
|
|
+|`std.range`|Определения и примитивы классификации, имеющие отно
|
|
|
+шение к диапазонам|
|
|
|
+|`std.regex`|Обработчик регулярных выражений|
|
|
|
+|`std.stdio`|Стандартные библиотечные средства ввода/вывода, построен
|
|
|
+ные на основе библиотеки stdio языка C. Входные и выходные
|
|
|
+файлы предоставляют интерфейсы в стиле диапазонов, благо
|
|
|
+даря чему многие алгоритмы, определенные в модуле std.algo
|
|
|
+rithm, могут работать непосредственно с файлами|
|
|
|
+|`std.string`|Функции, специфичные для строк. Строки тесно связаны
|
|
|
+с std.algorithm, так что модуль std.string, относительно не
|
|
|
+большой по размеру, в основном лишь ссылается (определяя
|
|
|
+псевдонимы) на части std.algorithm, применимые к строкам|
|
|
|
+|`std.traits`|Качества типов и интроспекция|
|
|
|
+|`std.typecons`|Средства для определения новых типов, таких как Tuple|
|
|
|
+|`std.utf`|Функции для манипулирования кодировками UTF|
|
|
|
+|`std.variant`|Объявление типа Variant, который является контейнером для
|
|
|
+хранения значения любого типа. Variant – это высокоуровне
|
|
|
+вый union|
|
|
|
+
|
|
|
+11.10. Встроенный ассемблер[^8]
|
|
|
+Строго говоря, большую часть задач можно решить, не обращаясь к столь
|
|
|
+низкоуровневому средству, как встроенный ассемблер, а те немногие за
|
|
|
+дачи, которым без этого не обойтись, можно написать и скомпилировать
|
|
|
+отдельно, после чего скомпоновать с вашей программой на D обычным
|
|
|
+способом. Тем не менее встроенный в D ассемблер – очень мощное сред
|
|
|
+ство повышения эффективности кода, и упомянуть его необходимо. Ко
|
|
|
+нечно, в рамках одной главы невозможно всеобъемлюще описать язык
|
|
|
+ассемблера, да это и не нужно – ассемблеру для популярных платформ
|
|
|
+посвящено множество книг[^9]. Поэтому здесь мы приводим синтаксис
|
|
|
+и особенности применения встроенного ассемблера D, а описание ис
|
|
|
+пользуемых инструкций оставим специализированным изданиям.
|
|
|
+К моменту написания данной книги компиляторы языка D существо
|
|
|
+вали для платформ x86 и x86-64, соответственно синтаксис встроенно
|
|
|
+го ассемблера определен пока только для этих платформ.
|
|
|
+
|
|
|
+11.10.1. Архитектура x86
|
|
|
+Инструкции ассемблера можно встроить в код, разместив их внутри
|
|
|
+конструкции asm:
|
|
|
+asm {
|
|
|
+naked;
|
|
|
+mov ECX, EAX;
|
|
|
+mov EAX, [ESP+size_t.sizeof*1];
|
|
|
+mov EBX, [ESP+size_t.sizeof*2];
|
|
|
+L1:
|
|
|
+mov DH, [EBX + ECX - 1];
|
|
|
+mov [EAX + ECX - 1], DH;
|
|
|
+loop L1;
|
|
|
+ret;
|
|
|
+}
|
|
|
+Внутри конструкции asm допустимы следующие сущности:
|
|
|
+• инструкция ассемблера:
|
|
|
+инструкция арг1, арг2, ..., аргn;
|
|
|
+•
|
|
|
+метка:
|
|
|
+метка:
|
|
|
+•
|
|
|
+псевдоинструкция:
|
|
|
+псевдоинструкция арг1, арг2, ..., аргn;
|
|
|
+•
|
|
|
+комментарии.
|
|
|
+Каждая инструкция пишется в нижнем регистре. После инструкции
|
|
|
+через запятую указываются аргументы. Инструкция обязательно за
|
|
|
+вершается точкой с запятой. Несколько инструкций могут распола
|
|
|
+гаться в одной строке. Метка объявляется перед соответствующей ин
|
|
|
+струкцией как идентификатор метки с последующим двоеточием. Пе
|
|
|
+реход к метке может осуществляться с помощью оператора goto вне бло
|
|
|
+ка asm, а также с помощью инструкций семейства jmp и call. Аналогично
|
|
|
+внутри блока asm разрешается использовать метки, объявленные вне
|
|
|
+блоков asm. Комментарии в код на ассемблере вносятся так же, как
|
|
|
+и в остальном коде на D, другой синтаксис комментариев недопустим.
|
|
|
+Аргументом инструкции может быть идентификатор, объявленный вне
|
|
|
+блока asm, имя регистра, адрес (с применением обычных правил адреса
|
|
|
+ции данной платформы) или литерал соответствующего типа. Адреса
|
|
|
+можно записывать так (все эти адреса указывают на одно и то же зна
|
|
|
+чение):
|
|
|
+mov EDX, 5[EAX][EBX];
|
|
|
+mov EDX, [EAX+5][EBX];
|
|
|
+mov EDX, [EAX+5+EBX];
|
|
|
+Также разрешается использовать любые константы, известные на эта
|
|
|
+пе компиляции, и идентификаторы, объявленные до блока asm:
|
|
|
+int* p = arr.ptr;
|
|
|
+asm
|
|
|
+{
|
|
|
+mov EAX, p[EBP];
|
|
|
+// Помещает в EAX значение p.
|
|
|
+mov EAX, p;
|
|
|
+// То же самое.
|
|
|
+mov EAX, [p + 2*int.sizeof]; // Помещает в EAX второй
|
|
|
+// элемент целочисленного массива.
|
|
|
+}
|
|
|
+Если размер операнда неочевиден, используется префикс тип ptr:
|
|
|
+add [EAX], 3;
|
|
|
+add [EAX], int ptr 3;
|
|
|
+// Размер операнда 3 неочевиден.
|
|
|
+// Теперь все ясно.
|
|
|
+Префикс ptr можно использовать в сочетании с типами near, far, byte,
|
|
|
+short, int, word, dword, qword, float, double и real. Префикс far ptr не ис
|
|
|
+пользуется в плоской модели памяти D. По умолчанию компилятор ис
|
|
|
+пользует byte ptr. Префикс seg возвращает номер сегмента адреса:
|
|
|
+mov EAX seg p[EBP];
|
|
|
+Этот префикс также не используется в плоской модели кода.
|
|
|
+Также внутри блока asm доступны символы: $, указывающий на адрес
|
|
|
+следующей инструкции, и __LOCAL_SIZE, означающий количество байт
|
|
|
+в локальном кадре стека.
|
|
|
+Для доступа к полю структуры, класса или объединения следует помес
|
|
|
+тить адрес объекта в регистр и использовать полное имя поля в сочета
|
|
|
+нии с offsetof:
|
|
|
+struct Regs
|
|
|
+{
|
|
|
+uint eax, ebx, ecx, edx;
|
|
|
+}
|
|
|
+void pushRegs(Regs* p)
|
|
|
+{
|
|
|
+asm {
|
|
|
+push EAX;
|
|
|
+mov EAX, p;
|
|
|
+// Помещаем в p.ebx значение EBX
|
|
|
+mov [EAX+Regs.ebx.offsetof], EBX;
|
|
|
+// Помещаем в p.ecx значение ECX
|
|
|
+mov [EAX+Regs.ecx.offsetof], ECX;
|
|
|
+// Помещаем в p.edx значение EDX
|
|
|
+mov [EAX+Regs.edx.offsetof], EDX;
|
|
|
+pop EBX;
|
|
|
+// Помещаем в p.eax значение EAX
|
|
|
+mov [EAX+Regs.eax.offsetof], EBX;
|
|
|
+}
|
|
|
+}
|
|
|
+Ассемблер x86 допускает обращение к следующим регистрам (имена
|
|
|
+регистров следует указывать заглавными буквами):
|
|
|
+AL AH AX EAXBP EBPES CS SS DS GS FS
|
|
|
+BL BH BX EBXSP ESPCR0 CR2 CR3 CR4
|
|
|
+CL CH CX ECXDI EDIDR0 DR1 DR2 DR3 DR6 DR7
|
|
|
+DL DH DX EDX
|
|
|
+STSI ESITR3 TR4 TR5 TR6 TR7
|
|
|
+ST(0) ST(1) ST(2) ST(3) ST(4) ST(5) ST(6) ST(7)
|
|
|
+MM0 MM1 MM2 MM3 MM4 MM5 MM6 MM7
|
|
|
+XMM0 XMM1 XMM2 XMM3 XMM4 XMM5 XMM6 XMM7
|
|
|
+Ассемблер D вводит следующие псевдоинструкции:
|
|
|
+align целочисленное_выражение;
|
|
|
+целочисленное_выражение должно вычисляться на этапе компиляции.
|
|
|
+align выравнивает следующую инструкцию по адресу, кратному це
|
|
|
+лочисленному_выражению, вставляя перед этой инструкцией нужное ко
|
|
|
+личество инструкций nop (от Not OPeration), имеющих код 0x90.
|
|
|
+even;
|
|
|
+Псевдоинструкция even выравнивает следующую инструкцию по
|
|
|
+четному адресу (аналогична align 2). Выравнивание может сильно
|
|
|
+повлиять на производительность в циклах, где часто выполняется
|
|
|
+переход по выравниваемому адресу.
|
|
|
+naked;
|
|
|
+Псевдоинструкция naked указывает компилятору не генерировать
|
|
|
+пролог и эпилог функции. В прологе, как правило, создается новый
|
|
|
+кадр стека, а в эпилоге размещается код возвращения значения. Ис
|
|
|
+пользуя naked, программист должен сам позаботиться о получении
|
|
|
+нужных аргументов и возвращении результирующего значения в со
|
|
|
+ответствии с применяемым функцией соглашением о вызовах.
|
|
|
+Также ассемблер D разрешает вставлять в код непосредственные значе
|
|
|
+ния с помощью псевдоинструкций db, ds, di, dl, df, dd, de, которые соот
|
|
|
+ветствуют типам byte, short, int, long, float, double и extended и соответ
|
|
|
+ственно размещают значения этого типа (extended – тип с плавающей
|
|
|
+запятой длиной 10 байт, известный в D как real). Каждая такая псевдо
|
|
|
+инструкция может иметь насколько аргументов. Строковый литерал
|
|
|
+в качестве аргумента эквивалентен указанию n аргументов, где n – дли
|
|
|
+на строки, а каждый аргумент соответствует одному знаку строки.
|
|
|
+Следующий пример делает то же самое, что и первый пример в этом
|
|
|
+разделе:
|
|
|
+asm {
|
|
|
+naked;
|
|
|
+db 0x89, 0xc1, 0x8b, 0x44, 0x24, 0x04, 0x8b;
|
|
|
+db 0x5c, 0x24, 0x08, 0x8a, 0x74, 0x0b, 0xff;
|
|
|
+db 0x88, 0x74, 0x08, 0xff, 0xe2, 0xf6, 0xc3; // Коротко и ясно.
|
|
|
+}
|
|
|
+Префиксы инструкций, такие как lock, rep, repe, repne, repnz и repz, ука
|
|
|
+зываются как отдельные псевдоинструкции:
|
|
|
+asm
|
|
|
+{
|
|
|
+rep;
|
|
|
+movsb;
|
|
|
+}
|
|
|
+Ассемблер D не поддерживает инструкцию pause. Вместо этого следует
|
|
|
+писать:
|
|
|
+rep;
|
|
|
+nop;
|
|
|
+Для операций с плавающей запятой следует использовать формат с дву
|
|
|
+мя аргументами.
|
|
|
+fdiv ST(1);
|
|
|
+fmul ST;
|
|
|
+fdiv ST,ST(1);
|
|
|
+fmul ST,ST(0);
|
|
|
+// Неправильно
|
|
|
+// Неправильно
|
|
|
+// Правильно
|
|
|
+// Правильно
|
|
|
+
|
|
|
+11.10.2. Архитектура x86-64
|
|
|
+Архитектура x86-64 является дальнейшим развитием архитектуры х86
|
|
|
+и в большинстве случаев сохраняет обратную совместимость с ней. Рас
|
|
|
+смотрим отличия архитектуры x86-64 от x86.
|
|
|
+Регистры общего назначения в x86-64 расширены до 64 бит. Их имена:
|
|
|
+RAX, RBX, RCX, RDX, RBP, RSI, RDI, RSP, RIP и RFLAGS, причем RIP теперь доступен
|
|
|
+из ассемблерного кода. Вдобавок добавились восемь 64-разрядных ре
|
|
|
+гистров общего назначения R8, R9, R10, R11, R12, R13, R14, R15. Для доступа
|
|
|
+к младшим 32 битам такого регистра к названию добавляется суф
|
|
|
+фикс D, к младшим 16 – W, к младшим 8 – B. Так, R8D – младшие 4 байта
|
|
|
+регистра R8, а R15B – младший байт R15. Также добавились восемь XMM-
|
|
|
+регистров XMM8–XMM15.
|
|
|
+Рассмотрим регистр RIP подробнее. Регистр RIP всегда содержит указа
|
|
|
+тель на следующую инструкцию. Если в архитектуре х86, чтобы полу
|
|
|
+чить адрес следующей инструкции, приходилось писать код вида:
|
|
|
+asm
|
|
|
+{
|
|
|
+call $;
|
|
|
+// Поместить в стек адрес следующей инструкции
|
|
|
+// и передать на нее управление.
|
|
|
+pop EBX;
|
|
|
+// Вытолкнуть адрес возврата в EBX.
|
|
|
+add EBX, 6;
|
|
|
+// Скорректировать адрес на размер
|
|
|
+// инструкций pop, add и mov.
|
|
|
+mov AL, [EBX]; // Теперь AL содержит код инструкции nop;
|
|
|
+nop;
|
|
|
+}
|
|
|
+то в x86-64 можно просто написать[^10]:
|
|
|
+asm
|
|
|
+{
|
|
|
+mov AL, [RIP]; // Загружаем код следующей инструкции.
|
|
|
+nop;
|
|
|
+}
|
|
|
+К сожалению, выполнить переход по содержащемуся в RIP адресу с по
|
|
|
+мощью jmp/jxx или call нельзя, равно как нельзя получить значение RIP,
|
|
|
+скопировав его в регистр общего назначения или стек. Впрочем, call $;
|
|
|
+как раз помещает в стек адрес следующей инструкции, что, по сути,
|
|
|
+идентично push RIP; (если бы такая инструкция была допустима). По
|
|
|
+дробную информацию можно найти в официальном руководстве по
|
|
|
+конкретному процессору.
|
|
|
+
|
|
|
+11.10.3. Разделение на версии
|
|
|
+По своей природе ассемблерный код является платформозависимым.
|
|
|
+Для х86 нужен один код, для x86-64 – другой, для SPARC – третий,
|
|
|
+а компилятор для виртуальной машины вообще может не иметь встроен
|
|
|
+ного ассемблера. Хорошая практика – реализовать требуемую функ
|
|
|
+циональность без использования ассемблера, добавив альтернативные
|
|
|
+реализации, оптимизированные для конкретных архитектур. Здесь
|
|
|
+пригодится механизм версий.
|
|
|
+Компилятор dmd определяет версию D_InlineAsm_X86, если доступен ас
|
|
|
+семблер х86, и D_InlineAsm_X86_64 если доступен ассемблер x86-64.
|
|
|
+Вот пример такого кода:
|
|
|
+void optimizedFunction(void* arg) {
|
|
|
+version(D_InlineAsm_X86) {
|
|
|
+asm {
|
|
|
+naked;
|
|
|
+mov EBX, [EAX];
|
|
|
+}
|
|
|
+}
|
|
|
+else
|
|
|
+version(D_InlineAsm_X86_64) {
|
|
|
+asm {
|
|
|
+naked;
|
|
|
+mov RBX, [RAX];
|
|
|
+}
|
|
|
+}
|
|
|
+else {
|
|
|
+size_t s = *cast(size_t*)arg;
|
|
|
+}
|
|
|
+}
|
|
|
+
|
|
|
+11.10.4. Соглашения о вызовах
|
|
|
+Все современные парадигмы программирования основаны на процедур
|
|
|
+ной модели. Каким бы ни был ваш код – функциональным, объектно-
|
|
|
+ориентированным, агентно-ориентированным, многопоточным, распре
|
|
|
+деленным, – он все равно будет вызывать процедуры. Разумеется, с по
|
|
|
+вышением уровня абстракции, добавлением новых концепций процесс
|
|
|
+вызова процедур неизбежно усложняется.
|
|
|
+Процедурный подход выгоден при организации взаимодействия фраг
|
|
|
+ментов программы, написанных на разных языках. Во-первых, разные
|
|
|
+языки поддерживают разные парадигмы программирования, а во-вто
|
|
|
+рых, даже одни и те же парадигмы могут быть реализованы по-разно
|
|
|
+му. Между тем процедурный подход является тем самым фундаментом,
|
|
|
+на котором основано все остальное. Этот фундамент надежен, стандар
|
|
|
+тизирован и проверен временем.
|
|
|
+Вызов процедуры, как правило, состоит из следующих операций:
|
|
|
+• передача аргументов;
|
|
|
+• сохранение адреса возврата;
|
|
|
+• переход по адресу процедуры;
|
|
|
+• выполнение процедуры;
|
|
|
+•
|
|
|
+•
|
|
|
+передача возвращаемого значения;
|
|
|
+переход по сохраненному адресу возврата.
|
|
|
+В высокоуровневом коде знать порядок выполнения этих операций не
|
|
|
+обязательно, однако при написании кода на ассемблере их придется
|
|
|
+реализовывать самостоятельно.
|
|
|
+То, как именно выполняются эти действия, определяется соглашения
|
|
|
+ми о вызовах процедур. Их относительно немного, они хорошо стандар
|
|
|
+тизированы. Разные языки используют разные соглашения о вызовах,
|
|
|
+но, как правило, допускают возможность использовать несколько со
|
|
|
+глашений. Соглашения о вызовах определяют, как передаются аргу
|
|
|
+менты (через стек, через регистры, через общую память), порядок пере
|
|
|
+дачи аргументов, значение каких регистров следует сохранять, как пе
|
|
|
+редавать возвращаемое значение, кто возвращает указатель стека на
|
|
|
+исходную позицию (вызывающая или вызываемая процедура). В сле
|
|
|
+дующих разделах перечислены основные из этих соглашений.
|
|
|
+
|
|
|
+11.10.4.1. Соглашения о вызовах архитектуры x86
|
|
|
+Архитектура x86 за долгие годы своего существования породила мно
|
|
|
+жество соглашений о вызовах процедур. У каждого из них есть свои
|
|
|
+преимущества и недостатки. Все они требуют восстановления значений
|
|
|
+сегментных регистров.
|
|
|
+cdecl
|
|
|
+Данное соглашение принято в языке C, отсюда и его название (C Decla
|
|
|
+ration). Большинство языков программирования допускают использо
|
|
|
+вание этого соглашения, и с его помощью наиболее часто организуется
|
|
|
+взаимодействие подпрограмм, написанных на разных языках. В язы
|
|
|
+ке D оно объявляется как функция с атрибутом extern(C). Аргументы
|
|
|
+передаются через стек в обратном порядке, то есть начиная с последне
|
|
|
+го. Последним в стек помещается адрес возврата. Значение возвращает
|
|
|
+ся в регистре EAX, если по размеру оно меньше 4 байт, и на вершине сте
|
|
|
+ка, если его размер превышает 4 байта. В этом случае значение в EAX
|
|
|
+указывает на него. Если вы используете псевдоинструкцию naked, вам
|
|
|
+придется обрабатывать переданные аргументы вручную.
|
|
|
+extern(C) int increment(int a) {
|
|
|
+asm {
|
|
|
+naked;
|
|
|
+mov EAX, [ESP+4]; // Помещаем в EAX значение a, смещенное на размер
|
|
|
+// указателя (адреса возврата) от вершины стека.
|
|
|
+inc EAX;
|
|
|
+// Инкрементируем EAX
|
|
|
+ret;
|
|
|
+// Передаем управление вызывающей подпрограмме.
|
|
|
+// Возвращаемое значение находится в EAX
|
|
|
+}
|
|
|
+}
|
|
|
+Стек восстанавливает вызывающая подпрограмма.
|
|
|
+pascal
|
|
|
+Соглашение о вызовах языка Паскаль в D объявляется как функция
|
|
|
+с атрибутом extern(Pascal). Аргументы передаются в прямом порядке,
|
|
|
+стек восстанавливает вызываемая процедура. Значение возвращается
|
|
|
+через передаваемый неявно первый аргумент.
|
|
|
+stdcall
|
|
|
+Соглашение операционной системы Windows, используемое в WinAPI.
|
|
|
+Объявление: extern(Windows). Аналогично cdecl, но стек восстанавлива
|
|
|
+ет вызываемая подпрограмма.
|
|
|
+fastcall
|
|
|
+Наименее стандартизированное и наиболее производительное соглаше
|
|
|
+ние о вызовах. Имеет две разновидности – Microsoft fastcall и Borland
|
|
|
+fastcall. В первом случае первые два аргумента в прямом порядке пере
|
|
|
+даются через регистры ECX и EDX. Остальные аргументы передаются че
|
|
|
+рез стек в обратном порядке. Во втором случае через регистры EAX, EDX
|
|
|
+и ECX передаются первые три аргумента в прямом порядке, остальные ар
|
|
|
+гументы передаются через стек в обратном порядке. В обоих случаях,
|
|
|
+если размер аргумента больше размера регистра, он передается через
|
|
|
+стек. Компиляторы D на данный момент не поддерживают данное согла
|
|
|
+шение, однако при использовании динамических библиотек есть воз
|
|
|
+можность получить указатель на такую функцию и вызвать ее с помо
|
|
|
+щью встроенного ассемблера.
|
|
|
+thiscall
|
|
|
+Данное соглашение обеспечивает вызов методов класса в языке С++.
|
|
|
+Полностью аналогично stdcall. Указатель на объект, метод которого
|
|
|
+вызывается, передается через ECX.
|
|
|
+Соглашение языка D
|
|
|
+Функция D гарантирует сохранность регистров EBX, ESI, EDI, EBP.
|
|
|
+Если данная функция имеет постоянное количество аргументов, пере
|
|
|
+менное количество гомогенных аргументов или это шаблонная функ
|
|
|
+ция с переменным количеством аргументов, аргументы передаются
|
|
|
+в прямом порядке и стек очищает вызываемая процедура. (В противном
|
|
|
+случае аргументы передаются в обратном порядке, после чего передает
|
|
|
+ся аргумент _arguments. _argptr не передается, он вычисляется на базе
|
|
|
+_arguments. Стек в этом случае очищает вызывающая процедура.) После
|
|
|
+этого в стеке резервируется пространство под возвращаемое значение,
|
|
|
+если оно не может быть возвращено через регистр. Последним передает
|
|
|
+ся аргумент this, если вызываемая процедура – метод структуры или
|
|
|
+класса, или указатель на контекст, если вызываемая процедура – деле
|
|
|
+гат. Последний аргумент передается через регистр EAX, если он умеща
|
|
|
+ется в регистр, не является трехбайтовой структурой и не относится
|
|
|
+к типу с плавающей запятой. Аргументы ref и out передаются как ука
|
|
|
+затель, lazy – как делегат.
|
|
|
+Возвращаемое значение передается так:
|
|
|
+• bool, byte, ubyte, short, ushort, int, uint, 1-, 2- и 4-байтовые структуры,
|
|
|
+указатели (в том числе на объекты и интерфейсы), ссылки – в EAX;
|
|
|
+• long, ulong, 8-байтовые структуры – в EDX (старшая часть) и EAX (млад
|
|
|
+шая часть);
|
|
|
+• float, double, real, ifloat, idouble, ireal – в ST0;
|
|
|
+• cfloat, cdouble, creal – в ST1 (действительная часть) и ST0 (мнимая
|
|
|
+часть);
|
|
|
+• динамические массивы – в EDX (указатель) и EAX (длина массива);
|
|
|
+• ассоциативные массивы – в EAX;
|
|
|
+• делегаты – в EDX (указатель на функцию) и EAX (указатель на кон
|
|
|
+текст).
|
|
|
+В остальных случаях аргументы передаются через скрытый аргумент,
|
|
|
+размещенный на стеке. В EAX в этом случае помещается указатель на
|
|
|
+этот аргумент.
|
|
|
+
|
|
|
+11.10.4.2. Соглашения о вызовах архитектуры x86-64
|
|
|
+С переходом к архитектуре x86-64 количество соглашений о вызовах су
|
|
|
+щественно сократилось. По сути, осталось только два соглашения о вы
|
|
|
+зовах – Microsoft x64 calling convention для Windows и AMD64 ABI convention
|
|
|
+для Posix.
|
|
|
+Microsoft x64 calling convention
|
|
|
+Это соглашение о вызовах очень напоминает fastcall. Аргументы пере
|
|
|
+даются в прямом порядке. Первые 4 целочисленных аргумента переда
|
|
|
+ются в RCX, RDX, R8, R9. Аргументы размером 16 байт, массивы и строки
|
|
|
+передаются как указатель. Первые 4 аргумента с плавающей запятой
|
|
|
+передаются через XMM0, XMM1, XMM2, XMM3. При этом место под эти аргумен
|
|
|
+ты резервируется в стеке. Остальные аргументы передаются через стек.
|
|
|
+Стек очищает вызывающая процедура. Возвращаемое значение переда
|
|
|
+ется в RAX, если оно умещается в 8 байт и не является числом с плаваю
|
|
|
+щей запятой. Число с плавающей запятой возвращается в XMM0. Если
|
|
|
+возвращаемое значение больше 64 бит, память под него выделяет вызы
|
|
|
+вающая процедура, передавая ее как скрытый аргумент. В этом случае
|
|
|
+в RAX возвращается указатель на этот аргумент.
|
|
|
+AMD64 ABI convention
|
|
|
+Данное соглашение о вызовах используется в Posix-совместимых опера
|
|
|
+ционных системах и напоминает предыдущее, однако использует боль
|
|
|
+ше регистров. Для передачи целых чисел и адресов используются реги
|
|
|
+стры RDI, RSI, RDX, RCX, R8 и R9, для передачи чисел с плавающей запятой –
|
|
|
+XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6 и XMM7. Если требуется передать ар
|
|
|
+гумент больше 64 бит, но не больше 256 бит, он передается по частям
|
|
|
+через регистры общего назначения. В отличие от Microsoft x64, для пере
|
|
|
+данных в регистрах аргументов место в стеке не резервируется. Возвра
|
|
|
+щаемое значение передается так же, как и в Microsoft x64.
|
|
|
+
|
|
|
+11.10.5. Рациональность
|
|
|
+Решив применить встроенный ассемблер для оптимизации программы,
|
|
|
+следует понимать цену повышения эффективности. Ассемблерный код
|
|
|
+трудно отлаживать, еще труднее сопровождать. Ассемблерный код об
|
|
|
+ладает плохой переносимостью. Даже в пределах одной архитектуры
|
|
|
+наборы инструкций разных процессоров несколько различаются. Более
|
|
|
+новый процессор может предложить более эффективное решение стоя
|
|
|
+щей перед вами задачи. А раз уж вы добиваетесь максимальной произ
|
|
|
+водительности, то, возможно, предпочтете скомпилировать несколько
|
|
|
+версий своей программы для различных целевых архитектур, напри
|
|
|
+мер одну переносимую версию, использующую только инструкции из
|
|
|
+набора i386, другую – для процессоров AMD, третью – для Intel Core.
|
|
|
+Для обычного высокоуровневого кода достаточно просто указать соот
|
|
|
+ветствующий флаг компиляции[^11], а вот в случае с ассемблером придет
|
|
|
+ся создавать несколько версий кода, делающих одно и то же, но разны
|
|
|
+ми способами.
|
|
|
+version(AMD)
|
|
|
+{
|
|
|
+version = i686;
|
|
|
+version = i386;
|
|
|
+}
|
|
|
+else version(iCore)
|
|
|
+{
|
|
|
+version = i686;
|
|
|
+version = i386;
|
|
|
+}
|
|
|
+else version(i686)
|
|
|
+{
|
|
|
+version = i386;
|
|
|
+}
|
|
|
+void fastProcess()
|
|
|
+{
|
|
|
+version(AMD) {
|
|
|
+// ...
|
|
|
+} else version(iCore) {
|
|
|
+// ...
|
|
|
+} else version(i686) {
|
|
|
+// ...
|
|
|
+} else version(i386)
|
|
|
+{
|
|
|
+// ...
|
|
|
+} else {
|
|
|
+// ...
|
|
|
+}
|
|
|
+}
|
|
|
+И все это ради того, чтобы выжать из функции fastProcess максимум
|
|
|
+производительности! Тут-то и надо задаться вопросом: а в самом ли деле
|
|
|
+эта функция является краеугольным камнем вашей программы? Мо
|
|
|
+жет быть, ваша программа недостаточно производительна из-за ошиб
|
|
|
+ки на этапе проектирования, и выбор другого решения позволит сэконо
|
|
|
+мить секунды процессорного времени – против долей миллисекунд,
|
|
|
+сэкономленных на оптимизации fastProcess? А может, время и, как
|
|
|
+следствие, деньги, которых требует написание ассемблерного кода, луч
|
|
|
+ше направить на повышение производительности целевой машины?
|
|
|
+В любом случае задействовать встроенный ассемблер для повышения
|
|
|
+производительности нужно в последнюю очередь, когда остальные
|
|
|
+средства уже испробованы.
|
|
|
+
|
|
|
[^1]: Прямой порядок байтов – от старшего к младшему байту. – *Прим. пер.*
|
|
|
[^2]: Обратный порядок байтов – от младшего к старшему байту. – *Прим. пер.*
|
|
|
[^3]: «Shebang» – от англ. *sharp-bang* или *hash-bang*, произношение символов `#!` – *Прим. науч. ред.*
|
|
|
[^4]: Текущие версии реализации позволяют включать модули на уровне классов и функций. – *Прим. науч. ред.*
|
|
|
+[^5]: В тексте / используется в качестве обобщенного разделителя; необходимо понимать, что реа льный разделитель зависит от системы.
|
|
|
+[^6]: Описание этой части языка не было включено в оригинал книги, но по
|
|
|
+скольку данная возможность присутствует в текущих реализациях языка,
|
|
|
+мы добавили ее описание в перевод. – Прим. науч. ред.
|
|
|
+[^7]: Фобос (Phobos) – больший из двух спутников планеты Марс. «Марс» – изна
|
|
|
+чальное название языка D (см. введение). Digital Mars (Цифровой Марс) –
|
|
|
+компания, разработавшая язык D и эталонную реализацию языка – ком
|
|
|
+пилятор dmd (от Digital Mars D). – Прим. науч. ред.
|
|
|
+[^8]: Описание этой части языка не было включено в оригинал книги, но по
|
|
|
+скольку эта возможность присутствует в текущих реализациях языка, мы
|
|
|
+добавили ее описание в перевод. – Прим. науч. ред.
|
|
|
+[^9]: Например, есть хороший учебник для вузов «Assembler» В. И. Юрова. –
|
|
|
+Прим. науч. ред.
|
|
|
+[^10]: Ассемблер dmd2.052 не поддерживает доступ к регистру RIP. Возможно,
|
|
|
+данная функция появится позже. Ну а пока вместо mov AL, [RIP]; вы можете
|
|
|
+написать мантру db 0x8A, 0x05; di 0x00000000;, тем самым сообщив свое же
|
|
|
+лание на языке процессора. Помните: если транслятор не понимает некото
|
|
|
+рые символы или инструкции, вы можете транслировать ассемблерный код
|
|
|
+в машинный сторонним транслятором и вставить в свой ассемблерный код
|
|
|
+числовое представление команды, воспользовавшись псевдоинструкциями
|
|
|
+семейства db. – Прим. науч. ред.
|
|
|
+[^11]: Компилятор dmd2.057 пока трудно назвать промышленным компилято
|
|
|
+ром, поэтому упомянутого механизма в нем пока нет, а вот компилятор язы
|
|
|
+ка C gcc предоставляет возможность указать целевую платформу. Это позво
|
|
|
+ляет получить максимально эффективный машинный код для данной
|
|
|
+платформы, при этом в код на языке C вносить изменения не нужно. Чита
|
|
|
+телям, нуждающимся в компиляторах D, способных генерировать более оп
|
|
|
+тимизированный код, следует обратить внимание на проекты GDC (GNU D
|
|
|
+compiler) и LDC (LLVM D compiler) компиляторов D, построенных на базе
|
|
|
+генераторов кода GCC и LLVM. – Прим. науч. ред.
|