From d6684cdd8ca546c484b03b6e38b81b6a815a3cf2 Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Thu, 2 Mar 2023 19:40:45 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B7=D0=B0=D0=BA=D0=BE=D0=BD=D1=87=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BD=D0=B0=2011.1.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 11-расширение-масштаба/README.md | 1656 ++++++++++++++++- .../images/image-11-1-1.png | Bin 0 -> 16431 bytes .../images/image-11-1-9.png | Bin 0 -> 27480 bytes 3 files changed, 1655 insertions(+), 1 deletion(-) create mode 100644 11-расширение-масштаба/images/image-11-1-1.png create mode 100644 11-расширение-масштаба/images/image-11-1-9.png diff --git a/11-расширение-масштаба/README.md b/11-расширение-масштаба/README.md index 2bafe63..a138934 100644 --- a/11-расширение-масштаба/README.md +++ b/11-расширение-масштаба/README.md @@ -82,7 +82,7 @@ import std.stdio; // Получить доступ к writeln и всему ос import widget; ``` -![]() +![image-11-1-1](images/image-11-1-1.png) ***Рис. 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. + +![image-11-1-9](images/image-11-1-9.png) + +Рис. 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. – Прим. науч. ред. diff --git a/11-расширение-масштаба/images/image-11-1-1.png b/11-расширение-масштаба/images/image-11-1-1.png new file mode 100644 index 0000000000000000000000000000000000000000..fc39a4d047dff6252ca9a6bd3fe5789d79951a17 GIT binary patch literal 16431 zcmcJ$byU^S_bqxvNhw7IX$(+ONd*Z(Kt;Nw73q}jkVYhw?rs5*?v@7W?(Rc3+;#lk zd*2`T-uJuXjq%RV;rRGD=M#Icz1CcF&h0BDA&Q4Xii1EP@ZP+BC4)eqB_I%2K4YQ5 z|1A5cQ^P;lpI<9kAQ1SCsJ~Z28Su#v2x`QeS1;vkV>TwNRD;*hFp;{rBolZT87U;g znqSZ~HT~>6K9#)3T;Eh!?@GQ`oRl=1|NWQLEx8m-p(tac3YYcq*ylGe?rqH;o{#e% z@Hre**4S+FW?YOuC`RZw8lS;3DZ{)0?spBws02R!wnOGkP>+GUmQHhLwDKQoaiLul8J?Z6%c7PtP2kd z`#oZmB@35n$%uMni|-vA9nHX3zqCtq^Cl&)V?<=6(xMoB44g+SWQ?a8q*^jCGOo0T zUtGo994_DJjOJ2SR<_<>MQ6sk$#=T28}1s(wn4FOIFw0LX%U{2WBfPC6Bk2hZ^xY0 z@nX+z|POJUF+>2niSu(9~Rfo6?S>b zzBl9UA|oTC(-Ae2q%F3cehux5Mz=cKRUNAxBw+v}7A`wiNd&i3R0xrW$yE9697JlD;%=eK8a@?79tAQkTpa-ES+Q%g(BQ1@_kLF_Rdd`ZC5S+S0%qBP`r z-1o?j`r415>S=3-e*aFha1G98{N19rH8zfAZAM4C5-Sl-lG3+=RxQwm4dU%6KlI-Sd3h(D=#XCAWQevo6va*y_uZghmi>rLP7W?BJMNC7 z;5LG3AE{4Ba63aIBa1%ABp4{lQOU&iAY@?3PVl^busISN9i5$=)Av(&_sUgFg;O%P zcaUsN>NEv8MO*yz!p6qJ#yJBK7EO+FiOE2@RcZr#;n5YlD%5l!Law3{z)QJGA>jZA z>wj}VZxc0MoTIY8zwaV@EAmHdsJIo3t!q|r@pMsP(ZpEU(!%26!s5lC0KfhDYEfuW z(f98k@wSGlVtL%_GG6AKDPahM(`N=eCbaS;&_ad2>CS+p|W z@!Nk)v#s!c`1eCGe2N#PpN6_H4i4nJ(Gc1mZr<_q%*>R0mc!|djS~)s)(5MCR^-PC zd3mOWhHE3KyYu?8OC3=h38pndqJivI7RlVih({FUTA2JCHV5l0@%-^sgl^V(s5wlg zlPENrFqkdfzPqv_*L($$>h9+Ko7cyu)Omk&kYdc(*f_K6US5BHKd0$LR%%X_**Qj5 z0m8NSWu4H7^~UhXNVZsXv2iB;y{W>YqN|G?_*I{2I5+2cBEGGmc z0#<~=(;IWiIXO-|>vslTA_U43rW0|^Oy_K0sJAQRWZ)-Z!EMH)I2_JbZ~xEp9UbrR z@bH|RoU+^>VOd0eC}AaDNt}L3 ztNM)UH_x+D3~MeW?Q~J-@&ylzSF}I{b1S}mqX|A2>$z< zQ&i8_2`B^UqAXTsn{G6Cp@!ULOC3^^G>f`V-e(3zqdlMR$)exLUwo(??2L99Z*e1a$9HPczr4HDB~P!Vsj0a|$-Ct><@A|9DL!69PR?>*k`dwR_&qRh zIXOA0U(U?0H;Rai;(jMPZLof9I9Vz6@(e#gx_IPoqoy5&92HnMd`>$lITbuK4;UB> zO_7^5-SI=cgZDg+_P5Cnm*N#o#}j(=>Tg$8R#jD2Vbc)YyH~$8er!f;S60TL$f!|Z z;^Y^`ghg{-#D9IP!sd{Ymz|wGZ?51EzzZ&*iVgWbZ(!=DV(lglqH3 z)7|krHZzV`1CK+n;=6|i2fyp+L>F&*1SuC8{Vw>eTx24Sm0z6y=H$l%#-oU3y{%j| z&kYHz{7iW1ZE3t0;ZFJL+u#tjN?>swUYI|qOOTv>wx z-7q5`o+adErDdh1F_TNn%Q7*YA9C4>GcgIiHZwDGbab3;_TVyA7v&55xz7C4_-7N> zK!5+SV|7TL_#26XgY6qs`M--VMzjzJVK+B#jh#!XzDoz{fQA`HwaRA~PL9E8;<2%@ zrw40?P*dAB1?BfEEwSZw7nwmCSiJpOS#HC)z`PRZ6I6t{w(J3 z5aJV2@XxK9?(X*X>{iIIa&`LVNHXE^8WuCz##R5^x5M6_JuQO!z{^Bi1EoKiJA>~A{FIQj@6HN1kGCFM>huOPGt z;Wx>YRrV(DYb0#b`%6npEz@I=W{;!mDw6qG=qAHxG z&UaP*l${>nA9O}>sT65G%nP9-#Dd(Xr=_*$XNrRmV4mi(eRNgLlezQz>N&>VP|v_a zk0&JG>Mn#l+$FlYI5|1%n}gHeO_bPlBNH2zEz5IaH~uh17epwL4dog36&qKb>|@hp zK7M?-H9;&=P-%;FkE=nApht>iTcJR?QepjbIZ`PjOQI zna)$2o8AdR1Mec)-v|q9j|EW3VO~Kzbd$U-!U_Sgn1*s1k!psTidGoGnwAM?J~Uf0Abvv{dCpbB*x~S8za{ z$3i@UoD*Wa7tzDb&3%PUcMzGXf2*UcK)1nU@{HlpYhm!VF~*N)~9FYrpj^$2q1?f`!A4qz4V?GHb9%l zt*s+UBfnj&Qw4hm`U(nv7ZwyG_vp##UO^~%yLo>N4GAIVw_g`*IX>K)l#NCA@l5qJ zE!mu-{l>+?kqWnOVTQCmy`3y-frbW^1Ou16^z-&CBp(tovN@s$58?lQR6CZDIUjEI zrFX~hao9{&er)iTrNBXG-NK=4v$z;9t3BSWu-;cI)yntv#U3yViH(a>7g8MZ%FTuQ zOc9Y+8nyNkY*U3XYIwPrUa1X~LLy}eF6~I3xm*mJ%|V?N@oc$;;r`V44=$bV(&J!S zrT5D3VqQe9zCuu2e7?mSl2e+Y*Y#6rqTEWfqMeb&bSnYNh?W zx16eKrC#gJ!V1RIPmP}y6^HN)homX4A++$}L9H*=%j$&A@ceNz@zE9%d-RCX-vyMq z@G~&@L4BuILDl!h#zyE!98QmEFM97Zw(=b8x^NWS(+xXzS>pnh-50<84yP%8*AztZ;(DvZClq zLd2|a5J60jY%{QlyS&%&@xyd<`u&JS@hL7k&76JL>8>HD;pAOAJG-AoVeD)c78b!Z z&N-7+#J6tYQmkvJWuFIlAP@uiioe~Xo@n;J#yMmr#QORb=P@oI@LegSK$Yq9=cZHD z13f*)%cH+Ku%Y(H-y|~LSpS)m(;gEP5fSkt#z;qJb9!0ejUWP{1W<_{2d^wBJe`QeMW+<+& zE#>4qEq)ps)dy1ExP6xFO)yqoahQs! z-gsk$^H4@r?$x#19cJd%_J^DjN6(%=xBDpdH`d`0iw0i$&f;RdbI#ut+4OU7yxZ1v zG>z?J4qKbNvqS-ue7|+4_n+kf1yknc(4iJ;4$$|!25fqGZ7ab3#z2zx zb$zrmdziP82UO_%{Et4@)_##om%5iO5%&{$@euN5>I-%0kKe)=v9rWPLLL=7&v9kgSt*@o;ftVlGwA>fAANR7w=2 zk2ke~095gwExok5?Rbn0MKR3r){7QeI=Xb5tyvVt{mfsVZaC!QZ0K>UXRtOoY$UinpD5!m=N8PH zMBl#s%E3`&xxd$V+1J$G?(%w{98>Wl3{Rw|BvQ(%iLZ*;ro%`hWX&{kmWI$;rj& zX$ES@|NAw&y39>Kku4dUW{>>(x0klg`ox@aXN98k&%g&Ei#aj&!8`j zgC-@|yE$%pqW2OZiM29Nad=#?^eVbZSU8b}_*FDLJ^fWo0{qD$F?RE%gAI769vx_z zp^H<0#US3Rm)ozb_;q186=*wGI;Z$Km6w4usHBxES4juMz1c@M4bmwHLvrsUtcq2h2 zorj)*erE3q%f0*8ad4^^<{eM=-pr~2mKtDCbZT#<;m>%2TKDy)NTirZ0A z+&@VV{i4~8>Fqf=9xhx|oz^!W9N^bC7h4xwTU*z^G&bIVE`D>gYWipbpqk0@7G(-z+lz>zZ zx_HLo{{xvkmg;JJ{1dsceEof&KIOPwbFE5piFrwo1t7Uh#M{Adr@NB8yj6J7od4<* zR&^3)8al+u|8{mMo#5kLjU_VxD>=*^>(4 z>@awffUnP%cV_-EJOwtdmd^H(J1|;+&_B}b9|De6oGLJlzx+#M$zA2A z7`{s^BrYzViT}`om5r^Vvy&kX6VY}p9@@HkvJU7U@(j!cwW z@9*qv&MquW&+8c&K%=9u?U$&GeRKx=9ZP)8comuRLa#QH@kVTgdzF)8Mi)AQ6$hQ* z_Ge2=+$zVF)ldeg-`Be0S?&|pN-MLI=13=t$fiq@u^L6BrO^(UAq1>pQES3V<#pI! z9myt+?vxN0@6=>{PR=}#7!vRmrxQ>|eeX@hRdj?tkO((PaJ_O+A10s;5D93{TJZnF z0RXvl3%Zg2<+JtSA9!r=`s2qdaPeEX&hGzLJSz}48chrf!lJ^w4kv}b zegVQzW#*#gK3NH-D=RD0u8e8+^9^8r^oZJHesPh{<=|WM+?$S%tdS_-M@H5@^Er!` zAK@w}Sl7UEOh~mgl&z@O;yD~K%rENCZMGiK=uu)iX}P~sPG~*@Iq#Ebpi;HvW|P6ayWxT?%oi7EJYyv$WL!EO+2Q$#G&Jrfhk^FnNwkwB z2-le4!0QvQ#%nH_mdH>T;++G~hW|RL`=Wt6Ya@LHV#E+>GX;^6kwne@ zQ#QziDtBHQEodQR>BRZ0_m*3Fl0AsjtL!-dB;{6DM{^pfA{D2<&ox65wYS=mR+UT1 zOcll|13b^n%sf9UOzKaoitLt7K3t!o8$}0R@C`oBWLq0OCpQK$X*P6H!mp?^J}axM z$K~ecqFR4w%{E4DY%kW8@+PZ1??a+@baX;71=7;ece%{!XVI?gag+`*+%~B=$93S5XlaJra-?mlqd5pK8v^s-Ihj0=8@! zTA)|d=z1YhqA z<;@aBm2)`Vxciu|e!;xr_y{ZInOh*C%uun3h`s%rH@-(7>YD>g>g%0pI1sKf0(C+I zJ@U(F7cTN@s&XJPd}FbWjGA6hl#AcH_tm$f#SeEQ}5)YGb6GhgRj{A9jtdIqsHka_jkKpi-p9>zblib9fF z$MZQUm*{kUQz}TAYC5Z}1%#{ijOrdC){*0d|Jo36X&A?+mjNYL5ecuM3QS!9MPWu?I$FEXlb(te_HDgOBL4p ze7}q%G*!%t^YThV=7$HC>|X{}aj~U=lfWNU7Dq z$nCiR<1uK)VA>l$Jo)DDzmj$2mz0)20)#T7`r~e>0C}eI*hF-sB&0GFIP!CLbsh zFafR2l~8c-r>fQEWEBwU?4~_2tBgnrai$z$v9SJJHQj}s1sJ>4)ip7zdqD)C!%+i# zBLF(zZcL%DqJ#9geLqn6=wDtMPfYCZ?LoCQM@-dUL5%z|sdC^sjH|K-tkcG(d-38n zsqy8c6V&-*S2s*4YpC_$ftTx2$}s>~BN9cosseMXLw|#8E=XAA&P?_|L5yN|b}REN)76De@443B1n8n>WZ%(Rbmva< zR=Am})?BdCDjeCuVRm@l>q`uVcUaV-NNh?iF@-x8omukGx-wJoJ3isy$oV{q(r#sC z1IhVmD|=Mu{`fmsuko4hJ?in0jOTmk_zs|Z>F0uvLd2jigFu^cacRAy!ey#Ik+z@m zSQd*$d}16I4=bfy69%ZJJJ=L|zXO-8bA)1(NuhwUzPh)YP^$?4Y2zjF;E z?t0k=J*`Kob-8g900=7%Hy(PtS5jh9DRUzNm1TDA8x$xHl$5imhY0jYo@b$0Hwa%a&|T2uA}g8K$7Hva*6AbiXr% zvv~Wc=@gk>I;(IR-!!gvI1ErxrZ=)l1gR#D$3dacZfP*5ORdslgSo#?9J_12Cfbs%hUzrC=NlPqew1`Eu{FN2X0xKOg_2F0vPI{H&v3cFB@a%nP zd0(h!>h%>Aq&JF{sxo^w+~B*23Q5ULqn&Kb%E~h7jPm8wXsR~qP4PWy-=H90-n*3e z>vFc7UlJI2bdA)1aeA4Mg5rEGH#?#JKQE4uY5kc0AV8tu*RR)a-rS*@**)%#=k@dV z6@T~o=w#opH=HFYJ>p#&uz-zGw3<`ozs%qNp`FT+Oz&%=)6&+C<+Iuu%z3i?e0!TJ z<#@=KogSh<1D5E&h1qksE9l6l;m8JL$&QCFlX9_MUz%~CIxiC=BkeZ|2K(Z|!jLlo zVb6o96I_2Z@omub+sghoOx-gUO`rNcVlt`*tjI@p*(dlaq%0* zQ7Z3CISgsJ^W&Q5Ht_N9s5N^L&Th;RS??|`NyZD`hdyw7YwlqH?%%(EJI=#Yy2#4N zDEYWP90bmx(ge%VS4u>ROrG=k!}c&d$|ng0is3(g`~bcd^z`|~3mez5BWxnK#mgeS zm7AYF&0*dkxVJfVxHh&leQ~~vc6oC$z#8cCWG@2UCnOX+R$qMs_$*HLYQMNQ+T@$> zX93M6LC!VVI`Wv7I}NVw5lE$9=;DGtG&D4IqtdY7=f)Ka9WJBglZ*3;>DUT|+^|Xm z!Q?;wP>PNZSJ?ji`D4VXA|xw2OL7`AKQ9_c8FozhFMa6!wx)X}r2`A8+-`1h|5vme zrZjVX-~7Q_8}~bufy7i)rXVF67}y_8)9}S}opkdHcZ*cnn(hAS{4qPL!xUw+HpvU= z7LxV($st$CFG7iy_Vx;=wZZIg7P9hkdwXlYC zx1H{S)T54r*V)$gyToRBbxeL%uvl=a+Q|CRy9HAbkNuxgMgKdi#UMJ@$7xNiJC771QnZ3Cp~}G zmoG1&d2l)S8Chl*dC3?=OXEsjX9_DVl-p{}Wb_ve)5g^OC7`;)e#G?l=d5?v$v4b} z0(ByG6w$o|HA!1a&m$8X|Y*yGyAU+NpbPVObf(ZVSRmrg6|>i;$oPMK_r&HNI_xY8N~*3QgZUAkNRJ%S!6BcZuyI#K*0Z~|G7@h z_|SfTUq&r=0&|l^Q8)#Ocu0p#-#(v(t&(3o^I>!h_J_x}OypXUsEeqp`4@(xXsZ zdS-eQeZJHkU$QYyVq6O=zInv7286`eK!FNLVQqy)xCA97O41u09LPD@3-xwiiy2_OYe+##_1*EMM)Xg zr`u)JSIjh^Bya&BYMRGVUcRQ<>Fnfy#^cv}MjhJV_NJbF4slu8J|HJF;%oB&4%JHD zy?YmXYYzuuj)^$}fam|sB}R^#t;K8Cz_}o3g_FvuJ6X9+L?;!Cb_)-{laE+~7ZI&Q z3b*GE<==WY25tyE1qkID5gmPOy#+GK!y@ZW(yaTgn!o}6pA0nrXKM|rs8mRh^8>(o zh|$;E3!a6v2L!IP1VW{r;NiM0G5<(SGB@ z#D+F3n77AM6Jpt>R+`g>Tr$$kH|H%h=^Ge;bjPBL7T?X{LJTQX3Q;yLWvhhbztI+C4$ z>Wcmf3b`z}sNQMq=!kh{;)txS0z~Z*1k$UiYDsmbimY^9!N~f{s~8ZfNlxDQ9zhUi zpF{$<%}(S1b9fR>f};7vVleT(=xTqC5LTJlM)_s!%OR8TgzR2VB9OTwLPM=MA9zTl zLze7{;+q5FI0>PXrem?#O|+@EzTCrq$5CBIkfU*F32^AHU z85Gp(jJ*AMeIEPcgFAj4I`gl=H8bOgKnG%BuVMJDgv(I%m57qZ zyuZMLSXw1IDoP@j)40zIZ_H-%R={|TLwEa_${qp!{rkyJKQ+faw<1L+z`;f*VATq$ zaq$kI$dpL5w8q3F`jSwuPjHX&r&5VRVJ3JsH|Fm1(X20aj#w=D2JUV4={A(_0~`_- z_UzQ*cRJG=+T4;bUF%EVf_mzKe!0HVr_&h?^omc_xao`=^~zLB>{!WQt_BNVZ?4cO zXym)LFJ|~jVSb429;-UK<(Zar#qx;NTt_^I>~L38AN2vPDm6JTFP`B*{HQx6Ed_9| zU*LOXrNza?ubja@sz*mhzDoWb7=YHQ13U>F9O7c)1-}aAli>dO(%=npggMI#M_3o- z<>f$%kUi52Lhkaqx}lPQ+&gjc>wY**JaM6&H{(C(nEcVyHq>`H{PN7Z32G{x2PC5W z-vti`JG$C_A6?pT+S}Xf^OI{Rsmd^Fzqoz-HgLE#Zf?NRv`31h_kmfjqQrvEqtbcb zVCjIuRJ}$$-yk%^;Y!f`X9TFX%eLMC0p2h00Ue#6>FDtAg9i_M0=0Cs^z;p{lMU7W z1p*a5QjUU}no0it{>Xe-K*|PLM+K>=shN+b{6q58&+Tts0e2ZNmwLN^7J%+a>I8{B zy4a-5{un3F+hX=5IK{3A(8Ig(*w6pc3vQTEt+AJP6a@bgi#pbAD(ou4(JE1bo-~`G zTt#NJJMo{TjRY(3HYl8&TGPes2+zzmE$O3^_!?OG&0np;zDUmkIDr|P z&DWJ*+ZTHE*jEnGs98`cHWZqAZC!25t#Kt)xsZmoNPbcFy1nI*-SbnMk6flW zTywLt668Fbi-5m~{)m&*@H{(eUveRE<8j#>2Uy$9d`a1uo1ENNpqS@RcEO9w$HQ~l zk|+eOkzP9TdH!dtJ9bAKE)10^DO#{5g45pJ5I_3V=)+rA7kT$?(szonVymqlAz^eE zA~&=o?38D`7>HZLF|MxTrc(!$&!lGF;d@&EO|qQv=+mc9-ORC(?{suT7UwfBIrQ)u zbSKB3Hwib9l9C=D+c7BQWKAV}Il@8B-*3JA@A=Zs>ecza+p9%}R+g5px_6GxLfgY# zd@es|YW|3g<U+Gi#L2kP{m)p`{s?Mb) z2vss8uH3qbB0}CMA)$sqR!~|0bpVCq*|TQ{n{#weVWK}jBi~*O3yP1=OH51z5o58y zk*_8pL15PB{4JQN=FOjBczN+hBbn&ZOH+Vun%BK+0M~RTa(s%8kns%+qjU&U8P;L&}Nt&WM**goqKh zssAuIkc|FWF8^1#MI!hrLdu|3qlWKn>7%{9@*OrYf%*MPdJOlEIyxp>AEpK;)N&JRs!6a&c$-Z)4SMTNiFpFU0P$>@@kfB0~{qXQW-uZ1V>&+kP*#c!4r7e_ge z_{a--0V!@rK9}7;;uSU<=B7iN%f53t%0)@p^nw&xIy&lQmimXe)9u788zVK{txB1B z)E=m(4(V!JXeU@7E73VG2WL~Um5t&zYe->{k^UFE-M^4Lz>b|`;-3&wNm|{bM=@0) z?F(YzD7S%rdzkb2sch^*$&)8fo_#(F3NGmLFq<1T;v6u01hp0+bq^r#c~p`6`FVx) zZYp=XX-ZmJvDN88$GgRz2GNWz!MhLut~a5l1f%#)X0GSviw{-j$Y@ALtv>WsQ`>Ohhqo0=~#2$EdG2&pRa*rDt}JS()v0cH5St;Qqre@Gh?LQG8ab8!9CRA4EYycjk2%s0|3gM9sK!Taol9 zSy|aESpoS+Eel|Bn6{DfVq3g@8+G;pK<(LC&6!WIRw41TrcdAzxPT=)AXj3ncoRPW zKXgJ&RdsxV$L6h=7>~`0rmpUxnC}cG>hXgt=T7!*Oy{s@dU|?-(R+*06!1BnE-WoEVd1WWN|W*QQ_7U^(FW*P#&{8IqSS#4!Xf6*MW7CN=@xa{bRa`IjJoLw?g0Y#Ik?~g^P+<@OT2zAa;7E zBK~)smTSMHGK7ik?jU(=Dl1M`|da&{wl zAfU;%rfP_&eEkD5iyZ~9Q1rRL?Qf)PI#Y2~Nu6Mr2xn0z=R70u)h|fvK3b}Cr_$IQ zDv<=OayIGS3~hWjzwLDGmRNQS_|y=H*FZ)50oDbZ2E;l5SOAb{JPJ+5(jY(9V>_Mf zIqh{fK${G!Nr`drAZIMCE5bxahc>^<9rFe-hsS14L`1=n1y@DQT ziFa~}8<3PI@ozmO<)-~vcuh%lc%O%d2i)Rx7<=*-O2D zXh@^nF|xp~J#cM(q$CfrpZ2CE7=;cF4nWYsrb+(u&CGC-wmNUMe&#b|sca{uGE|sJ z-AmKaQF^E2*+)7&rufWTB-!rt$oBLogsnM8slYtWcs%6|*i2qUu8B!HT&UEVa->qmigbalDU3=X%AS2!6SP(G;Rm`UnY1Jsw;iI zt*y|ClF!0^ER0F6Ig=6`7fQQ} z4g~ArrNlYrsEt*R9y>XmKi^&KjN{>&nQIOS2}!EImqjE`A?~T8si|D!%zITx2#l~e zsEf*gm2;nrJ!2~2%D~Hns2OUW|DrDbdk@m^cBh>qGq4jvw+=8-u31_H7o!0Lxhoq> zhkn#^M?=i$|s&d zLG*e534CY{wVsevo$diUCs*6n)|OnbuxpYJ(C}z=}I!p8OavKZ(DRCPr%+5~X#9m%r9zo=HvJF^7 zQLlO9i-3S0dzS1O9Z->A!<9=vQZzDR%nK2<`g-|SkNw_R-p{wFrjskshR?mV zsd(66f*qcP4gu?UhMZ|hY|yB2*4BG6J|5*y&il3Q16Dl1gv-?d20T*dN}EiXJZ z*y<+C;3)pKt-IUC+N`y$4X4J5?(yR{YHCiS-&Q<=#>dBbN=&b(gUkWy+>c=ga0MtU zM|pZSP3c|)_y!CP48*c)e?6M@2y%0Cb3Z-gw>xXBt(^|M(E#?+*zoAJ_1Syp4vvI3 z%w~tT$H!HNMEq&RdT~87|Cx)0g(};lm}^zy(`|TG`T9EV*es zaQNp1*&GE42;B6mmqqkv{IMxbJ~=&CRZ0iJZ^*oo)O$=#4jth^$SfXcpQRP~klv ztwB`-Ta>b7Tl~A@t6#H;qiA;nn?BtRDfyhePJJ94oV3mu=@CV24ol-lMm97Kn5Xl3 ztx275_jHMP+R^^r!JUmk@slQv8hbVHX91Pd(}MhdAGJ7GwPxpAuaU=5D4l_&{Pcvb85^s>D_V1kX*vn|e5s!IQEi|Dxc121@V?@ZNUlea z&DNx+m&e}fq|?&ogg+@)R{y=MEP2z&9hOqv4kHpq=Zo|zK;NP zOGisa+~?omoI@a{pnVi4c0SnQfc8r_tZkehOse_ID)DOm9u4_L{=e zr#-Vanoh?%+SQinBrVq=>JW}ltDzg-MJ5W(x&v}C8me}0e0No-VXw{6Bv<4@$Xm6> z`W(za^_{SF>2p~ORXTb{%<)j=F@FF|*4W6v^(y2R{|zu0fN$gWI-kQ49t!`$##fw( zr=QwV-Y_W_KJa*9zb6HvhV!(>%|2j^BRTcW$D>Q2iS`us1d6)S$uULg(om*%*3%D* z#*vQ&Bt6*wzR|b$U4X;s0a#T% zf*_2*Z5{gDyLm1(H7zV8WO-!`jEdJ;tVDgMV|n)X_C3R^zyA=Rwk0M8nX9)___{k4 zepdQ8_$kWm?KB7~MrtRkXGi$KMG3(W9b8ZlTwrl*0UL0{bCfxPMaO}x7wU>tsWQpY zdob(VIIA66vCHT$61>RU_W0k%%bn(&$gu)lNw9H>p`5ZflVyErVg8Lph9Tttipa8_ zq2j8eS)_1!pJeQFi^qGj(^sx~9TPxcs#TlA?vboWWQH?^+C)|rxkeAFMzC4zezP

oF1v(KS#T*b2z@V5nS>SNE=ufu&sS@`qtiSqU zvq?24=pl9))~4TreW`7Ee5An7?GC$1uNFiP7!MM;@d*X7f?LO{wbvs1(j88ZbKY0I zRZ}}xR1iYd$4rnpb5yHm!A-iqI%GOj9SdqvSQsaGCvSK+fS<5OS{n9ju(5Gmk6>#C z7mbF-9Td3f2T^1a=el3O*SBKgOeYt2@}<}S;)=f$huyp7mfJha>#ZN2WN9m*mW?Y~ o{Y!Yd|NS;yRPp`)^~|zMX$@1vEc!YP+z9bTSmIT#;0L$=2GJNz)&Kwi literal 0 HcmV?d00001 diff --git a/11-расширение-масштаба/images/image-11-1-9.png b/11-расширение-масштаба/images/image-11-1-9.png new file mode 100644 index 0000000000000000000000000000000000000000..e53f6b880bb6dbae737bf28dc7382df8f19fbaf0 GIT binary patch literal 27480 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 XUsu8`{;&!@9U=cf>3+^#L%;t5G8q{Q literal 0 HcmV?d00001