авторефераты диссертаций БЕСПЛАТНАЯ БИБЛИОТЕКА РОССИИ

КОНФЕРЕНЦИИ, КНИГИ, ПОСОБИЯ, НАУЧНЫЕ ИЗДАНИЯ

<< ГЛАВНАЯ
АГРОИНЖЕНЕРИЯ
АСТРОНОМИЯ
БЕЗОПАСНОСТЬ
БИОЛОГИЯ
ЗЕМЛЯ
ИНФОРМАТИКА
ИСКУССТВОВЕДЕНИЕ
ИСТОРИЯ
КУЛЬТУРОЛОГИЯ
МАШИНОСТРОЕНИЕ
МЕДИЦИНА
МЕТАЛЛУРГИЯ
МЕХАНИКА
ПЕДАГОГИКА
ПОЛИТИКА
ПРИБОРОСТРОЕНИЕ
ПРОДОВОЛЬСТВИЕ
ПСИХОЛОГИЯ
РАДИОТЕХНИКА
СЕЛЬСКОЕ ХОЗЯЙСТВО
СОЦИОЛОГИЯ
СТРОИТЕЛЬСТВО
ТЕХНИЧЕСКИЕ НАУКИ
ТРАНСПОРТ
ФАРМАЦЕВТИКА
ФИЗИКА
ФИЗИОЛОГИЯ
ФИЛОЛОГИЯ
ФИЛОСОФИЯ
ХИМИЯ
ЭКОНОМИКА
ЭЛЕКТРОТЕХНИКА
ЭНЕРГЕТИКА
ЮРИСПРУДЕНЦИЯ
ЯЗЫКОЗНАНИЕ
РАЗНОЕ
КОНТАКТЫ


Pages:     | 1 |   ...   | 10 | 11 || 13 | 14 |   ...   | 15 |

«АНДРЕЙ АЛЕКСАНДРЕСКУ Язык программирования D 16 лет вместе с профессионалами The D Programming ...»

-- [ Страница 12 ] --

• если идентификатор найден более чем в одном модуле и этот иден­ тификатор является именем функции, применяется механизм раз­ решения имен при кроссмодульной перегрузке (см. раздел 5.5.2).

Привлекательным следствием такого подхода является то, что клиент­ ский код обычно мож ет быть кратким, а многословным только тогда, когда это действительно необходимо. В преды дущ ем примере ф ункция main.d могла вызвать fun проще, без каких-либо «украшений»:

/ / Содержимое main.d import widget;

void main() { fun(10);

/ / Все в порядке, идентификатор fun определен / / только в модуле widget ) Пусть в файле io.d так ж е определена ф ункция fun с похож ей сигнату­ рой:

/ / Содержимое io.d из каталога acme/goodies void fun(long n) { } И пусть модуль с функцией main включает и файл widget.d, и файл io.d.

Тогда «неприукрашенный» вызов fun окаж ется ошибочным, но уточ­ ненные вызовы с указанием имени модуля по-преж нему будут работать нормально:

/ / Содержимое main.d im portw idget, acme.goodies.io;

void main() { fun(10);

/ / Ошибка!

/ / Двусмысленный вызов функции fun():

/ / идентификатор fun найден в модулях widget и acme.goodies.io 408 Глава 11. Расширение масштаба w idget.fun(10);

/ / Все в порядке, точное указание acm e.goodies.io.fun(10);

/ / Все в порядке, точное указание Обратите внимание: сама собой двусмысленность не проявляется. Если вы не попытаетесь обратиться к идентификатору в двусмысленной фор­ ме, компилятор никогда не пож алуется.

11.1.3.1. Кроссмодульная перегрузка функций В разделе 5.5.2 обсуж дается вопрос перегрузки функций в случае их расположения в разных модулях и приводится пример, в котором моду­ ли, определяющ ие функцию с одним и тем ж е именем, вовсе не обязательно порождают двусмысленность. Теперь, когда мы уж е знаем больше о модулях и модульности, пора поставить точку в этом разговоре.

Угон ф ункций (fu n ction 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 - уж асное наруш ение модульности.

Неудивительно, что язы ки программирования остерегаются угона. С++ разреш ает угон ф ункций, но большинство руководств по стилю про­ граммирования советуют этого приема избегать;

а Python, как и мно­ гие другие язы ки, и вовсе запрещ ает любой угон. С другой стороны, пе­ реизбыток воздерж ания м ож ет привести к излиш не строгим правилам, воспитывающ им привычку использовать в именах длинные строки идентификаторов.

D разреш ает проблему угона оригинальным способом. Основной руково­ дящ ий принцип подхода D к кроссмодульной перегрузке состоит в том, что добавление или уничтож ение включаемых модулей не должно вли­ ять на разреш ение имени ф ункции. Возня с инструкциями import мо­ ж ет привести к тому, что ранее компилируемые модули перестанут ком­ пилироваться, а ранее некомпилируемые модули станут компилируе­ мыми. Опасный сценарий, который D исключает, - тот, при котором, 11.1. Пакеты и модули поиграв с объявлениями import, вы оставите программу ком пилируе­ мой, но с разными результатами разреш ения имен при перегрузке.

Для любого вызова ф ункции, найденного в модуле, справедливо, что если имя этой функции найдено более чем в одном модуле и если вызов может сработать с версией функции из любого модуля, то такой вызов ошибочен. Если ж е вызов мож но заставить работать лиш ь при одном ва­ рианте разрешения имени, такой вызов легален, поскольку при таких условиях нет угрозы угона.

Вприведенном примере, где widget определяет fun(int), aacme.goodies.io — fun(long), положение дел в модуле main таково:

im portw idget, 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, видимыми из модулей, включающ их 410 Глава 11. Расширение масштаба widget.d (внимание) к а к будт о widget.donpedejiun и х са м. По сути, public import добавляет в widget.d объявление a lia s для каж дого идентифика­ тора из io.d. (Дублирование кода объектов не происходит, только неко­ торое дублирование идентификаторов.) П редполож им, что модуль io.d определяет ф ункцию print(string), а в функцию main.d поместим сле­ дую щ ий код:

import widget;

void main() { / / Все в порядке, идентификатор p r i n t найден pri nt ( ' ' 3f l paBCTByi T) ;

widget.print(''3flpaBCTByn");

/ / Все в порядке, widget фактически / / определяет p r in t } Что если на самом деле включить в main и модуль acme.goodies.io? По­ пробуем это сделать:

import widget;

import acme.goodies.io;

/ / Излишне, но безвредно void main() { ргШС'Здравствуй");

/ / Все впорядке.

/ /... в порядке.

wi dge t. p r i n t (" 3 f l p a e c T By ^ ' ) ;

acme. goodi es. i o. pri nt ("3Apa BCTBy^' );

/ /.. и в порядке!

} М одулю io.d вред не нанесен: тот факт, что модуль widget определяет псевдоним для acme.goodies.io, ни в коей мере не влияет на исходный идентификатор. Дополнительный псевдоним - это просто альтернатив­ ное средство получения доступа к одному и тому ж е определению.

Н аконец, в некотором более старом коде мож но увидеть объявления private import. Такая форма использования допустима и аналогична обычному объявлению import.

11.1.5. Объявления static import И ногда добавление включаемого модуля в неявный список для поиска идентификаторов при объявлении import (в соответствии с алгоритмом из раздела 11.1.3) мож ет быть нежелательным. Бывает уместным жела­ ние осущ ествлять доступ к определенному в модуле функционалу толь­ ко с явным указанием полного имени (а-ля имямодуля.имяидентификатора, а не имяидентификатора).

П ростейш ий случай, когда такое решение оправданно, —использование очень популярного модуля в связке с модулем узкого назначения при совпадении ряда идентификаторов в этих модулях. Например, в стан­ дартном модуле std.string определены широко используемые функции для обработки строк. Если вы взаимодействуете с устаревшей систе­ мой, применяющ ей другую кодировку (например, двухбайтный набор знаков, известный как DBCS - Double B yte Character Set), то захотите 11.1. Пакеты и модули использовать идентификаторы из std.string в большинстве случаев, а идентификаторы из собственного модуля dbcs_string - лиш ь изредка и с точным указанием. Д ля этого нуж но просто указать в объявлении import для dbcs_string ключевое слово sta tic:

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.strin g, первый вызов просто не компилировался бы. Д ля s ta tic import поиск идентификаторов не автоматизируется, даж е когда идентификатор не­ двусмысленно разреш ается.

Бывают и другие ситуации, когда конструкция s ta tic import м ож ет быть полезной. Сдержать автоматический поиск и использовать более много­ словный, но одновременно и более точный подход м ож ет пож елать и м о­ дуль, включающий множ ество други х модулей. В таких сл учая х клю ­ чевое слово s ta tic полезно использовать с целыми списками значений, разделенных запятыми:

static import teleport, time_travel, warp;

Или располагать его перед контекстом, заключенным в скобки, с тем ж е результатом:

static { import teleport;

import time_travel, warp;

} 11.1.6. Избирательные включения Другой эффективный способ справиться с конфликтующ ими иденти­ фикаторами - включить лиш ь определенные идентификаторы из моду­ ля. Для этого используйте следую щ ий синтаксис:

/ / Содержимое nain.d import widget ;

fun, gun;

Избирательные включения обладают точностью хирургического л а зе­ ра: данное объявление import вводит ровно два идентификатора - fun и gun. После избирательного включения невидим д а ж е идентификатор widget! П редположим, модуль widget определяет идентификаторы fun, gun и hun. В таком случае fun и gun можно будет использовать только так, будто их определил сам модуль main. Любые другие попытки, такие как hun, widget.hun и даж е widget.fun, незаконны:

412 Глава 11. Расширение масштаба / / Содержимое main.d Import widget : fun, gun;

void main() { fun();

/ / Все в порядке gun();

/ / Все в порядке hun();

/ / Ошибка!

w idg et.fun ();

//Ошибка!

widget.hun();

//Ошибка!

Высокая точность и контроль, предоставляемые избирательным вклю­ чением, сделали это средство довольно популярным - есть программи­ сты, не приемлющ ие ничего, кроме избирательного включения;

особен­ но много таких среди тех, кто преж де работал с языками, обладающи­ ми более слабыми механизм ам и включения и управления видимостью.

И все ж е необходимо отметить, что другие упомянуты е выше механиз­ мы уничтож ения двусмысленности, которые предоставляет D, ничуть не менее эффективны. Полный контроль над включаемыми идентифи­ каторами был бы гораздо более полезен, если бы механизм поиска иден­ тификаторов, используемы й D по умолчанию, не был безошибочным.

11.1.7. Включения с переименованием Больш ие проекты имеют тенденцию создавать запутанные иерархии пакетов. Чрезмерно ветвистые структуры каталогов - довольно частый артефакт разработки, особенно в проектах, где заранее вводят щедрую, всеобъемлю щ ую схем у именования, способную сохранить стабильность да ж е при непредвиденны х добавлениях в проект. Вот почему нередки ситуации, когда модулю приходится использовать очень глубоко вло­ ж енны й модуль:

import u t i l. c o n t a i n e r. f i n i t e. l i n e a r. l i s t ;

В таких случаях мож ет быть весьма полезно включение с переименова­ нием, позволяющее присвоить сущ ности u til.con tain er.fin ite.linear.lis t короткое имя:

import l i s t = u t i l. c o n t a i n e r. f i n i t e. l i n e a r. l i s t ;

С таким объявлением import программа мож ет использовать идентифи­ катор list.sym bol вместо чересчур длинного идентификатора util.con tain er.fin ite.lin ear.list.sym b ol. Если исходить из того, что модуль, о ко­ тором идет речь, определяет класс List, в итоге получим:

import l i s t = u t i l. c o n t a i n e r. f i n i t e. l i n e a r. l i s t ;

void main() { auto ls t1 = new l i s t. L i s t ;

/ / Все в порядке auto l s t 2 = new u t i l. c o n t a i n e r. f i n i t e. l i n e a r. l i s t. L i s t ;

/ / Ошибка!

/ / Идентификатор u t i l не определен!

11.1. Пакеты и модули auto l s t 3 = new List;

/ / Ошибка!

/ / Идентификатор L ist не определен!

} Включение с переименованием не делает видимыми переименованные пакеты (то есть u til, container,..., lis t ), так что попытка использовать ис­ ходное длинное имя в определении lst2 завершается неудачей при поис­ ке первого ж е идентификатора u til. Кроме того, включение с переимено­ ванием, безсом нения, обладаетстатической природой (см. раздел 11.1.5) в том смысле, что не использует механизм автоматического поиска;

вот почему не вычисляется выражение new List. Если вы действительно х о ­ тите не только переименовать идентификаторы, но ещ е и сделать их ви­ димыми, очень удобно использовать конструкцию a lia s (см. раздел 7.4):

import u t i l. c o n t a i n e r. f i n i t e. l i n e a r. l i s t ;

/ / Нестатическое включение a l i a s u t i l. c o n t a i n e r. f i n i t e. l i n e a r. l i s t l i s t ;

/ / Для удобства void main() { auto lst1 = new l i s t. L i s t ;

/ / Все в порядке auto l s t 2 = new u t i l. c o n t a i n e r. f i n i t e. l i n e a r. l i s t. L i s t ;

/ / Все в порядке = new List;

/ / Все в порядке auto ls t } Переименование такж е мож ет использоваться в связке с избирательны­ ми включениями (см. раздел 11.1.6). Продемонстрируем это на примере:

import s t d. s t d i o : say = w riteln ;

void main() { эауС'Здравствуй, мир!");

// Все в порядке, вызвать w rite ln s t d. s t d i o. БауС'Здравствуй, мир");

// Ошибка!

writeln(''3flpaBCTBy^ мир!");

// Ошибка!

s t d. stdio.w riteln(''3flpaBCTByn, мир! ");

// Ошибка!

} Как и ож идалось, применив избирательное включение, которое одно­ временно ещ е и переименовывает идентификатор, вы делаете видимым лишь включаемый идентификатор и ничего больше.

Наконец, можно переименовать и модуль, и включаемый идентиф ика­ тор (включаемые идентификаторы):

import io = s t d. s t d i o ;

say = w riteln, CFile = File;

Возможные взаимодействия м еж ду двумя переименованными включен­ ными идентификаторами могли бы вызвать некоторые противоречия.

Язык D решил этот вопрос, просто сделав преды дущ ее объявление тож ­ дественным следующим:

import io = s t d. s t d i o : w rite ln, File;

import s t d. s t d i o : say = w rite ln, CFile = File;

Дважды переименовывающее объявление import эквивалентно двум дру­ гим объявлениям. Первое из этих объявлений переименовывает только 414 Глава 11. Расширение масштаба модуль, а второе —только включаемый идентификатор. Таким образом, новая семантика определяется в терминах более простых, уж е извест­ ны х видов инструкции import. Преды дущ ее определение вводит иден­ тификаторы io.w riteln, io.F ile, say и CFile.

11.1.8. Объявление модуля Как говорилось в разделе 11.1, по той простой причине, что import при­ нимает лиш ь идентификаторы, пакеты и модули на D, которые предпо­ лагается хоть когда-либо включать в другие модули с помощью этой конструкции, долж ны иметь имена, являющ иеся допустимыми иден­ тификаторами язы ка D.

В отдельных ситуациях требуется, чтобы модуль замаскировался име­ нем, отличным от имени файла, где расположен код модуля, и притво­ рился бы, что путь до пакета, которому принадлеж ит модуль, отлича­ ется от пути до каталога, где на самом деле располагается упомянутый файл. Очевидная ситуация, когда это мож ет понадобиться: имя модуля не является допустимы м идентификатором D.

П редполож им, вы пиш ете программу, которая придерживается более широкого соглаш ения по именованию, предписывающего использовать дефисы в имени файла, например gnome-cool-app.d. Тогда компилятор D откаж ется компилировать ее, даж е если сама программа будет полно­ стью корректной. И все потому, что во время компиляции D должен ге­ нерировать информацию о каж дом модуле, каж ды й модуль должен об­ ладать допустимым именем, а gnome-cool-app не является таковым. Про­ стой способ обойти это правило - хранить исходный код под именем дпоте-соо1-арр,ана этапесборки переименовывать его, например в gnome_ cool_app.d. Этот трюк, конечно, сработает, но есть способ проще и луч­ ше: достаточно вставить в начало файла объявление модуля, которое выглядит так:

module gnome_cool_app;

Если такое объявление присутствует в gnome-cool-app.d (но обязательно в качестве первого объявления в файле), то компилятор будет доволен, поскольку он генерирует всю информацию о модуле, используя имя gnome_cool_app. В таком случае истинное имя вообще никак не проверя­ ется;

в объявлении модуля имя мож ет быть хоть таким:

module p a th.to.n o n e x is te n t.lo c a tio n.a p p ;

Тогда компилятор сгенерирует всю информацию о модуле, как будто он называется app.d и расположен в каталоге path/to/nonexistent/location.

К омпилятору все равно, потому что он не обращ ается по этому адресу:

поиск файлов ассоциируется исключительно с import, а здесь, при непо­ средственной ком пиляции gnome-cool-app.d, никаких включений нет.

11.1. Пакеты и модули 11.1.9. Резюме модулей Язык D поощряет модель разработки, которая не требует отделения объ­ явлений от сущ ностей, определяемы х программой (в С и С++ эти поня­ тия фигурируют как «заголовки» и «исходные коды»). Вы просто рас­ полагаете код в модуле и включаете этот модуль в другие с помощью конструкции import. Тем не менее иногда хочется принять другую мо­ дель разработки, предписывающ ую более ж есткое разделение м еж ду сигнатурами, которые модуль долж ен реализовать, и кодом, который стоит за этими сигнатурами. В этом случае потребуется работать с так называемыми резю ме м одулей (m odule su m m aries), построенными на основе исходного кода. Резюме модуля - это миним ум того, что необхо­ димо знать модулю о другом модуле, чтобы использовать его.

Резюме модуля - это фактически модуль без комментариев и реализа­ ций функций. Реализации функций, использую щ их параметры време­ ни компиляции, тем не менее в резю ме модуля остаю тся. Ведь ф ункции с параметрами времени ком пиляции долж ны быть доступны во время компиляции, так как могут быть вызваны непредвиденным образом в модуле-клиенте.

Резюме модуля состоит из корректного кода на D. Например:

/* Это документирующий комментарий для этого модуля -/ module acm e.doitall;

/** Это документирующий комментарий для класса А */ c la ss А { void fun() { } f i n a l void gun() {.} } c la ss B(T) { void hun() { } } void foo() { } void b a r ( in t n ) ( f l o a t x) { При составлении резюме модуля d o ita ll этот модуль копируется, но ис­ ключаются все комментарии, а тела всех ф ункций зам еняю тся на ;

(ис­ ключение составляют ф ункции с параметрами времени ком пиляции такие функции остаются нетронутыми):

416 Глава 11. Расширение масштаба module a c m e. d o i t a l l ;

class А { void f u n ( ) ;

f i n a l void gun();

c l a s s B(T) { void hun() { void f oo( ) ;

void b a r ( i n t n ) ( f l o a t x) { } Резю ме содерж ит информацию, необходимую другому модулю, чтобы использовать acm e.doitall. В больш инстве случаев резю ме модулей ав­ томатически вычисляются внутри работающего компилятора. Но ком­ пилятор мож ет сгенерировать резюме по исходному коду и по вашему запросу (в случае эталонной реализации компилятора dm для этого пред­ d назначен флаг -H). Сгенерированные резюме полезны, когда вы, к приме­ ру, хотите распространить библиотеку в виде заголовков плюс скомпи­ лированная библиотека.

Зам етим, что исключение тел ф ункций все ж е не гарантировано. Ком­ пилятор волен оставлять тела очень коротких функций в целях инлай нинга. Например, если функция acm e.doitall.foo обладает пустым те­ лом или просто вызывает другую функцию, ее тело мож ет присутство­ вать в сгенерированном интерфейсном файле.

П одход к разработке, хорошо знакомый программистам на С и С++, за­ ключается в сопровождении заголовочных файлов (то есть резюме) и файлов с реализациям и вручную и по отдельности. Если вы изберете этот способ, работать придется несколько больше, но зато вы сможете поупраж няться в коллективном руководстве. Например, право изме­ нять заголовочные файлы мож ет быть закреплено за командой проек­ тировщиков, контролирующей все детали интерфейсов, которые моду­ ли предоставляют друг другу. А команду программистов, реализующих эти интерфейсы, можно наделить правом изменять файлы реализации и правом на чтение (но не изменение) заголовочных файлов, используе­ мы х в качестве текущ ей документации, направляющ ей процесс реали­ зации. Компилятор проверяет, соответствует ли реализация интерфей­ су (ну, по крайней мере синтаксически).

С языком D у вас есть выбор - вы можете: 1) вообще обойтись без резюме модулей, 2) разреш ить компилятору сгенерировать и х за вас, 3) сопро­ вождать модули и резю ме модулей вручную. Все примеры в этой книге выбирают вариант 1) - не использовать резю ме модулей, оставив все заботы компилятору. Чтобы опробовать две другие возможности, вам 11.1. Пакеты и модули сначала потребуется организовать модули так, чтобы их иерархия соот­ ветствовала изображ енной на рис. 11.2.

Рис. 11.2. Структура каталога для отделения резюме модулей («заголовков») от файлов реализации Чтобы использовать пакет acme, потребуется добавить родительский ка­ талог каталогов acme и acme_impl к базовым путям поиска модулей про­ екта (см. раздел 11.1.2), а затем включить модули из acme в клиентский код с помощью следую щ их объявлений:

/ / Из модуля c l i e n t. 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 будут использовать объявление:

/ / Из модуля ac m e_ im pl/io/file.d module acme.i o. f i l e ;

418 Глава 11. Расширение масштаба Эти строки позволят компилятору сгенерировать долж ны е имена паке­ тов и модулей. Чтобы во время сборки программы компилятор нашел тела ф ункций, просто передайте ему файлы реализации:

% dmd c l i e n t. d /p a th /to /a cm e _ im p l/a lg eb ra.d Директива import в clien t.d обнаруж ит интерфейсный файл acme.di в ка­ талоге /path/to/acme. А компилятор найдет файл реализации точно там, где указано в командной строке, с корректными именами пакета и мо дуля.

Если коду из clien t.d потребуется использовать множество модулей из пакета acme, станет неудобно указывать все эти модули в командной строке компилятора. В таких случаях лучш ий вариант - упаковать весь код пакета acme в бинарную библиотеку и передавать dm только ее. Син­ d таксис для сборки библиотеки зависит от реализации компилятора;

ес­ ли вы работаете с эталонной реализацией, вам потребуется сделать что то типа этого:

% cd /p a th /to /a cm e _ im p l % dmd - l i b - o f a c m e a l g e b r a. d g u i. d io /file.d io /n e tw o r k.d Флаг -lib предписывает компилятору собрать библиотеку, а флаг -of (от output file - файл вывода) направляет вывод в файл acme.lib (Windows) или acme.a (U N IX -подобные системы). Чтобы клиентский код мог рабо­ тать с такой библиотекой, нуж н о ввести что-то вроде:

% dmd c l i e n t. d a c m e.lib Если библиотека acme широко используется, ее можно сделать одной из библиотек, которые проект использует по умолчанию. Но тут уж е мно­ гое зависит от реализации компилятора и от операционной системы, так что для успеха операции придется прочесть это ж уткое руководство.

11.2. Безопасность П онятие безопасности языков программирования всегда было противо­ речивым, но за последние годы его определение удивительно кристал­ лизовалось.

И нтуитивно понятно, что безопасный язы к тот, который «защищает свои собственные абстракции» [46, гл. 1]. В качестве примера таких аб­ стракций D приведем класс:

class А { int x;

} и массив:

flo a t[] array;

По правилам язы ка D (тоже «абстракция», предоставляемая языком) изменение внутреннего элемента x любого объекта типа А не должно из­ менять какой-либо элемент массивааггау, и наоборот, изменение array[n] 11.2. Безопасность для некоторого n не долж но изменять элемент x некоторого объекта ти­ па А. Как ни благоразумно запрещ ать такие бессмысленные операции, в D есть способы заставить их обе выполниться - формируя указатели с помощью cast или задействуя union.

void mai n( ) { f l o a t [ ] a r r a y = new float[1024];

auto obj = cast(A) a r r a y. p t r ;

Изменение одного из элементов массива аггау (какого именно, зависит от реализации компилятора, но обычно второго или третьего) изменяет obj.x.

11.2.1. Определенное и неопределенное поведение Кроме только что приведенного примера с сомнительным приведением указателя на flo a t к ссылке на класс есть и другие ошибки времени ис­ полнения, свидетельствующие о том, что язы к наруш ил определенные обещания. Хорошими примерами могут послуж ить разыменование ука­ зателя null, деление на ноль, а такж е извлечение вещественного квад­ ратного корня из отрицательного числа. Н икакая корректная програм­ ма не долж на когда-либо выполнять такие операции, и тот факт, что они все ж е могут иметь место в программе, типы которой проверяются, можно рассматривать как несостоятельность системы типов.

Проблема подобного критерия корректности, который «хорошо было бы принять»: список ошибок бесконечно пополняется. D сводит свое по­ нятие безопасности к очень точному и полезному определению: безопас­ ная программа на D характеризуется только определенным поведени­ ем. Различия м еж ду определенным и неопределенным поведением:

• определенное поведение: выполнение фрагмента программы в задан ­ ном состоянии заверш ается одним из заранее определенны х исхо­ дов;

один из возмож ны х исходов —резкое прекращ ение выполнения (именно это происходит при разыменовании указателя null и при д е­ лении на ноль);

• неопределенное поведение: эффект от выполнения фрагмента про­ граммы в заданном состоянии не определен. Это означает, что мож ет произойти все, что угодно в пределах ф изических возмож ностей.

Хороший пример - только что упомянуты й случай с cast: програм­ ма с такой «раковой клеткой» некоторое время м ож ет продолжать работу, но наступит момент, когда какая-нибудь запись в array с по­ следую щим случайны м обращением к obj приведет к тому, что ис­ полнение выйдет из-под контроля.

(Неопределенное поведение перекликается с понятием недиагностиро ванных ошибок, введенным Карделли [15]. Он выделяет две большие категории ошибок времени исполнения: диагностированные и недиаг 420 Глава 11. Расширение масштаба ностированные ошибки. Диагностированные ошибки вызывают немед­ ленный останов исполнения, а недиагностированные - выполнение про­ извольных команд. В программе с определенным поведением никогда не возникнет недиагностированная ошибка.) У противопоставления определенного поведения неопределенному есть пара интересны х нюансов. Рассмотрим, к примеру, язык, определяю­ щ ий операцию деления на ноль с аргументами типа int, так что она дол ж н а всегда порождать значение int.max. Такое условие переводит де­ ление на ноль в разряд определенного поведения - хотя данное опреде­ ление этого действия и нельзя назвать полезным. Примерно в том ж е ключе std.math в действительности определяет, что операция sqrt(-1) дол ж н а возвращать double.nan. Это так ж е определенное поведение, по­ скольку double.nan - вполне определенное значение, которое является частью специф икации язы ка, а так ж е ф ункции sq rt. Д аж е деление на ноль - не ош ибка для типов с плавающ ей запятой: этой операции забот­ ливо предписывается возвращать или плюс бесконечность, или минус бесконечность, или N aN («нечисло») (см. главу 2). Результаты выполне­ ния программ всегда будут предсказуемы ми, когда речь идет о функ­ ции s q rt или делении чисел с плавающ ей запятой.

Программа безопасна, если она не порождает неопределенное поведение.

11.3.2. Атрибуты @safe, @trusted и @system Н ехитрый способ гарантировать отсутствие недиагностированных оши­ бок - просто запретить все небезопасные конструкции D, например осо­ бые случаи применения вы ражения cast. Однако это означало бы невоз­ мож ность реализовать на D многие системы. Иногда бывает очень нуж ­ но переступить границы абстракции, например, рассматривать область пам яти, имеющей некоторый тип, как область памяти с другим типом.

Именно так поступают менеджер памяти и сборщик мусора. В задачи язы ка D всегда входила способность выразить логику такого программ­ ного обеспечения на системном уровне.

С другой стороны, многие прилож ения нуж даю тся в небезопасном до­ ступе к пам яти лишь в сильно инкапсулированной форме. Язык может заявить о том, что он безопасен, д а ж е если его сборщик мусора реализо­ ван на небезопасном языке. Ведь с точки зрения безопасного языка нет возмож ности использовать сборщик небезопасным образом. Сборщик сам по себе инкапсулирован внутри библиотеки поддержки времени ис­ полнения, реализован на другом язы ке и воспринимается безопасным языком как волшебный примитив. Любой недостаток безопасности сборщ ика мусора был бы проблемой реализации языка, а не клиентско­ го кода.

Как мож ет большой проект обеспечить безопасность большинства своих модулей, в то ж е время обходя правила в некоторых избранных случаях?

П одход D к безопасности - предоставить пользователю право самому 11.2. Безопасность решать, чего он хочет: вы мож ете на уровне объявлений заявить, при­ держивается ли ваш код правил безопасности или ем у н уж н а возм ож ­ ность переступить ее границы. Обычно информация о свойствах моду­ ля указывается сразу ж е после объявления модуля, как здесь:

module my_widget;

0 sa fe :

В этом месте определяются атрибуты @safe, @trusted и ©system, которые позволяют модулю объявить о своем уровне безопасности. (Такой под­ ход не нов;

в языке Модула-3 применяется тот ж е подход, чтобы отли­ чить небезопасные и безопасные модули.) Код, размещенный после атрибута @safe, обязуется использовать ин­ струкции лишь из безопасного подмнож ества D, что означает:

никаких преобразований указателей в неуказатели (например, int), • и наоборот;

• никаких преобразований м еж ду указателями, типы которых не им е­ ют отношения друг к другу;

• проверка границ при любом обращ ении к массиву;

• никаких объединений, включающ их указатели, классы и массивы, а такж е структуры, которые содержат перечисленные запрещ енны е типы в качестве внутренних элементов;

• никаких арифметических операций с указателями;

• запрет на получение адреса локальной переменной (на самом деле, требуется запрет утечки таких адресов, но отследить это гораздо сложнее);

• функции долж ны вызывать лиш ь ф ункции, обладаю щ ие атрибутом @safe или @trusted;

• никаких ассемблерных вставок;

никаких преобразований типа, лиш аю щ их данны е статуса const, • immutable или shared;

никаких обращений к каким-либо сущ ностям с атрибутом @system.

• Иногда эти правила могут оказаться излиш не строгими;

например, в стремлении избеж ать утечки указателей на локальные переменные можно исключить из рядов безопасны х программ очевидно корректные программы. Тем не менее безопасное подмножество D (по прозвищ у SafeD) все ж е довольно мощное - целые прилож ения могут быть полно­ стью написаны на SafeD.

Объявление или группа объявлений могут заявить, что им, напротив, требуется низкоуровневый доступ. Такие объявления долж ны содер­ жать атрибут @system:

0system:

void * a l l o c a t e ( s i z e _ t s i z e ) ;

422 Глава 11. Расширение масштаба void deallocate(void* p);

Атрибут @system действенно отключает все проверки, позволяя исполь­ зовать необузданную мощь языка - на счастье или на беду.

Н аконец, подход библиотек нередко состоит в том, что они предлагают клиентам безопасные абстракции, подспудно используя небезопасные средства. Такой подход применяют многие компоненты стандартной библиотеки D. В таких объявлениях можно указывать атрибут @trusted.

М одулям без какого-либо атрибута доступен уровень безопасности, на­ значаемый по умолчанию. Выбор уровня по умолчанию можно настро­ ить с помощью конфигурационных файлов компилятора и флагов ко­ мандной строки;

точная настройка зависит от реализации компилятора.

Эталонная реализация компилятора dmd предлагает атрибут по умолча­ нию @system;

задать атрибут по умолчанию @safe можно с помощью фла­ га командной строки -safe.

В момент написания этой книги SafeD находится в состоянии а-версии, так что порой небезопасные программы проходят компиляцию, а без­ опасные —нет, но мы активно работаем над решением этой проблемы.

11.3. Конструкторы и деструкторы модулей Иногда модулям требуется выполнить какой-то инициализирующ ий код для вычисления некоторых статических данных. Сделать это мож­ но, вставляя явные проверки («Были ли эти данные добавлены?») везде, где осущ ествляется доступ к соответствующ им данным. Если такой подход неудобен/неэф фективен, помогут конструкторы модулей.

П редполож им, что вы пиш ете модуль, зависящ ий от операционной сис­ темы, и поведение этого модуля зависит от флага. Во время компиляции легко распознать основные платформы (например, «Я Мас» или «Я РС»), но определять версию W indow s придется во время исполнения.

Чтобы немного упростить задачу, условимся, что наш код различает лиш ь ОС W indow s V ista и более поздние или ранние версии относитель­ но нее. Пример кода, определяющ его вид операционной системы на эта­ пе иниц иализаци и модуля:

p r iv a t e enum WinVersion { preVista, v is t a, postV ista } p r iv a te WinVersion winVersion;

sta tic th is() { OSVERSIONINFOEX info;

info.dwOSVersionInfoSize = OSVERSIONINFOEX.sizeof;

GetVersionEx(&info) | | a s s e r t ( f a l s e ) ;

i f (info.dwMajorVersion 6) { winVersion = WinVersion.preVista;

11.3. Конструкторы идеструкторы модулей } else i f (info.dwMajorVersion == 6 & info.dwMinorVersion == 0) { & winVersion = WinVersion.vista;

else { winVersion = WinVersion.postVista;

} } Этот геройский подвиг совершает конструктор модуля s t a t i c t h i s ( ). Та­ кие конст рукт оры м одулей всегда выполняются до main. Любой задан­ ный модуль мож ет содержать любое количество конструкторов.

В свою очередь, синтаксис деструкторов модулей предсказуем:

/ / На уровне модуля static ^this() { } Статические деструкторы выполняются после того, как выполнение main завершится каким угодно образом, будь то нормальный возврат или по­ рождение исключения. Модули могут определять любое количество де­ структоров модуля и свободно чередовать конструкторы и деструкторы модуля.

11.3.1. Порядок выполнения в рамках модуля Порядок выполнения конструкторов модуля в рамках заданного моду­ ля всегда соответствует последовательности располож ения этих кон­ структоров в модуле, то есть сверху вниз (лексический порядок). Поря­ док выполнения деструкторов модуля - снизу вверх (обратный лексиче­ ский порядок).

Если один из конструкторов модуля не смож ет выполниться и породит исключение, то не будет выполнена и функция main. Выполняются лишь статические деструкторы, лексически расположенные вы ш е отказавш е­ го конструктора модуля. Если не см ож ет выполниться и породит ис­ ключение какой-либо деструктор модуля, остальные деструкторы вы­ полнены не будут, а прилож ение прекратит свое выполнение, выведя сообщение об ошибке в стандартный поток.

11.3.2. Порядок выполнения при участии нескольких модулей Если модулей несколько, определить порядок вызовов слож нее. Эти пра­ вила идентичны определенным для статических конструкторов классов (см. раздел 6.3.6) и исходят из того, что модули, включаемые другими модулями, долж ны инициализироваться первыми, а очищаться - по­ следними. Вот правила, определяющ ие порядок выполнения статиче­ ских конструкторов модулей модуль1 и модуль2:

424 Глава 11. Расширение масштаба • конструкторы или деструкторы модулей определяются только в од­ ном из модулей модуль1 и модуль2, тогда не нуж но заботиться об упо­ рядочивании;

• модуль1 не включает модуль модуль2, а модуль2 не включает модуль1:

упорядочивание не регламентируется - любой порядок сработает, поскольку модули не зависят друг от друга;

• модуль! включаетмодуль2:конструкторымодуля2выполняютсядокон структоров модуля1, а деструкторы модуля2 - после деструкторов моду­ ля!;

• модуль2 включает модуль1: конструкторы модуля1 выполняются до конструкторов модуля2, а деструкторы модуля1 - после деструкторов модуля2;

• модуль1 включаетмодуль2,амодуль2 вкючаетмодуль1:диагностируется ош ибка «циклическая зависимость» и выполнение прерывается на этапе загрузки программы.

Проверка на циклическую зависимость модулей в настоящий момент делается во время исполнения. Такие циклы можно отследить и во вре­ мя ком пиляции или сборки, но это мало что дает: проблема проявляет­ ся в том, что программа отказывается загруж аться, и можно предполо­ ж ить, что перед публикацией программа запускается хотя бы один раз.

Тем не менее чем раньше обнаруж ена проблема, тем лучше, так что язы к оставляет реализации возможность выявить это некорректное состояние и сообщ ить о нем.

11.4. Документирующие комментарии П исать документацию скучно, а для программиста нет ничего страш­ нее скуки. В результате документация обычно содерж ит скупые, непол­ ные и устаревш ие сведения.

Автоматизированны е построители документации стараются вывести м аксимум информации из чистого кода, отразив заслуж ивающ ие вни­ м ания отнош ения м еж ду сущ ностями. Тем не менее современным авто­ матизированны м построителям нелегко задокументировать высоко­ уровневые намерения по реализации. Современные языки помогают им в этом, предписывая использовать так называемые документирующ ие ком м ент арии - особые комментарии, описывающие, например, опре­ деленную пользователем сущ ность. Языковой процессор (или сам ком­ пилятор, или отдельная программа) просматривает комментарии вме­ сте с кодом и генерирует документацию в одном из популярных форма­ тов (таком как XM L, HTML или PDF).

D определяет для документирую щ их комментариев спецификацию, описывающ ую формат комментариев и процесс и х преобразования в це­ левой формат. Сам процесс не зависит от целевого формата;

транслятор, 11.5. Взаимодействие с С и С++ управляемый простым и гибким шаблоном (такж е определяемы м поль­ зователем), генерирует документацию фактически в любом заданном формате.

Всеобъемлющее изучение системы трансляции документирую щ их ком­ ментариев не входит в задачу этой книги. Зам ечу только, что вам не по­ мешает уделить этому больше внимания;

документация многих проек­ тов на D, а такж е веб-сайт эталонной реализации компилятора и его стандартной библиотеки полностью сгенерированы на основе докумен­ тирующих комментариев D.

11.5. Взаимодействие с С и С++ Модули на D могут напрям ую взаимодействовать с ф ункциям и С и С++.

Есть ограничение: к этим функциям не относятся обобщ енные ф унк­ ции С++, поскольку для этого компилятор D долж ен был бы включать полноценный компилятор С++. Кроме того, схема располож ения полей класса D не совместима с классами С++, использую щ ими виртуальное наследование.

Чтобы вызвать функцию С или С++, просто укаж ите в объявлении функ­ ции язык и не забудьте связать ваш модуль с соответствующ ими биб­ лиотеками:

extern(C) i n t foo(char*);

extern(C++) double bar(double);

Эти объявления сигнализирую т компилятору, что вызов генерируется с соответствующими схемой располож ения в стеке, соглаш ением о вы­ зовах и кодировкой имен (такж е называемой декорированием имен — name m angling), д а ж е если сами ф ункции D отличаются по всем или некоторым из этих пунктов.

Чтобы вызвать функцию на D из программы на С или С++, просто д о ­ бавьте в реализацию одно из приведенных выше объявлений:

extern(C) i n t foo(char*) {.. / / Реализация } extern(C++) double bar(double) { / / Реализация } Компилятор опять организует необходимое декорирование имен и ис­ пользует соглашение о вызовах, подходящ ее для языка-клиента. То есть эту функцию мож но с одинаковым успехом вызывать из модулей как на D, так и на «иностранных языках».

426 Глава 11. Расширение масштаба 11.5.1. Взаимодействие с классами С++ Как у ж е говорилось, D не способен отобразить классы С++ в классы D.

Это связано с различием реализаций м еханизма наследования в этих язы ках. Тем не менее интерфейсы D очень похож и на классы С++, по­ этом у D реализует следую щ ий механизм взаимодействия с классами С++:

/ / Код на С++ c l a s s Foo { public:

v i r t u a l i n t method(int а, i n t b) { return а + b;

} };

Foo» newFoo() { return new Foo();

} void deleteFoo(Foo* obj) { d e l e te obj:

} / / Код на D extern (С++) { in te r f a c e Foo { i n t method(int, i n t ) ;

} Foo newFoo();

void deleteFoo(Foo);

) void main() { auto obj = newFoo;

s c o p e(ex it) deleteFoo(obj);

assert(obj.m ethod(2, 3) == 5);

} Следующ ий код создает класс, реализую щ ий интерфейс С++, и исполь­ зует объект этого интерфейса в вызове внешней функции С++, прини­ маю щ ей в качестве аргумента указатель на объект класса С++ Foo.

1 Описание этой части языка не было включено в оригинал книги, но по­ скольку данная возможность присутствует в текущих реализациях языка, мы добавили ее описание в перевод. - Прим. науч. ред.

11.6. Ключевое слово deprecated extern (С++) void call(Foo);

/ / В коде С++ эта функция должна быть определена как void call(Foo* f);

extern (С++) interface Foo { int bar(int, int);

} c l a s s FooImpl : Foo { extern (С++) int bar(int а, int b) { // } } void main() { FooImpl f = new FooImpl();

call(f);

11.6. Ключевое слово deprecated Перед любым объявлением (типа, функции или данны х) м ож ет распо­ лагаться ключевое слово deprecated. Оно действует как класс памяти, но нисколько не влияет собственно на генерацию кода. Вместо этого deprecated лишь информирует компилятор о том, что помеченная им сущность не предназначена для использования. Если такая сущ ность все ж е будет использована, компилятор выведет п редупреж ден ие или даж е откаж ется компилировать, если он был запущ ен с соответствую ­ щим флагом (-w в случае dmd).

Ключевое слово deprecated служ ит для планомерной постепенной м и­ грации от старых версий A PI к более новым версиям. П ричисляя соот­ ветствующие объявления к устаревшим, можно настроить компилятор так, чтобы он или принимал, или отклонял объявления с префиксом deprecated. Подготовив очередное изменение, отключите компиляцию deprecated - ошибки точно укаж ут, где требуется ваше вмешательство, что позволит вам ш аг за шагом обновить код.

11.7. Объявления версий В идеальном мире, как только программа написана, ее мож но зап ус­ кать где угодно. А здесь, на Земле, то и дело что-то заставляет вносить в программу изменения - другая версия библиотеки, сборка для особых целей или зависимость от платформы. Чтобы помочь справиться с этим, D определяет объявление версии version, позволяющ ее компилировать код в зависимости от определенных условий.

Способ использования версии намеренно прост и прямолинеен. Вы или устанавливаете версию, или проверяете ее. Сама версия м ож ет быть или целочисленной константой, или идентификатором:

428 Глава 11. Расширение масштаба version = 20100501;

version = F in a lR e le a se ;

Чтобы проверить версию, напишите:

version(20100501) { / / Объявления } versio n ( P r e F i n a l R e l e a s e ) { / / Объявления } e l s e version ( F i n a l R e l e a s e ) { / / Другие объявления } else { / / Еще объявления Если версия у ж е присвоена, «охраняемые» проверкой объявления ком­ пилирую тся, иначе они игнорируются. Конструкция version может включать блок e lse, назначение которого очевидно.

Установить версию м ож но лиш ь до того, как она будет прочитана. По­ пытки установить версию после того, как она была задействована в про­ верке, вызывают ош ибку времени компиляции:

version ( P r o E d it i o n ) {.. / / Объявления } version = P roE d ition ;

/ / Ошибка!

Р еакция такова, поскольку присваивания версий не предназначены для того, чтобы версии изменять: версия долж на быть одной и той ж е независимо от того, на какой фрагмент программы вы смотрите.

Указывать версию мож но не только в файлах с исходным кодом, но и в командной строке компилятора (например, -version=123 или -versi on=xyz в случае эталонной реализации компилятора dmd). Попытка уста­ новить версию как в командной строке, так и в файле с исходным кодом так ж е приведет к ошибке.

Простота семантики version не случайна. Было бы легко сделать кон­ струкцию version более мощной во многих отнош ениях, но очень скоро она начала бы работать наперекор своему предназначению. Например, управление версиями С с помощью связки директив # if/# elif/# else, без­ условно, позволяет реализовать больше тактик в определении версий именно поэтому управление версиями в проекте на С обычно содержит змеины й клубок условий, направляющ их компиляцию. Конструкция version язы ка D намеренно ограничена, чтобы с ее помощью можно бы­ ло реализовать лиш ь простое, единообразное управление версиями.

Компиляторы, как водится, имеют множество предопределенных вер­ сий, таких как платформа (например, Win32, Posix или Mac), порядок бай­ тов (LittleEndian, BigEndian) и так далее. Если включено тестирование 11.8. Отладочные объявления модулей, автоматически задается проверка version(unittest). Особыми идентификаторами времени и сп ол н ен и я FILEи _ LINEобознача­ _ ются соответственно имя текущ его файла и строка в этом файле. Пол­ ный список определений version приведен в документации вашего ком­ пилятора.

11.8. Отладочные объявления Отладочное объявление - это лиш ь особая версия с идентичны м син­ таксисом присваивания и проверки. Конструкция debug была определе­ на специально для того, чтобы стандартизировать порядок объявления отладочных режимов и средств.

Типичный случай использования конструкции debug:

mymodule;

module void f u n () { i n t x;

debug(mymodule) w r it e ln (" x = " x);

} Чтобы отладить модуль mymodule, укаж и те в командной строке при ком­ пиляции этого модуля ф лаг^еЬ ид=ту|^и1е, и вы ражение debug(mymodule) вернет true, что позволит скомпилировать код, «охраняемый» соответ­ ствующей конструкцией debug. Если использовать debug(5), то «охраняе­ мый» этой конструкцией код будет включен при уровне отладки = 5.

Уровень отладки устанавливается либо присваиванием debug целочис­ ленной константы, либо флагом компиляции. Д опустимо так ж е ис­ пользовать конструкцию debug без аргументов. Код, следую щ ий за та­ кой конструкцией, будет добавлен, если ком пиляция запущ ена с фла­ гом -debug. Как и в случае version, нельзя присваивать отладочной вер­ сии идентификатор после того, как он у ж е был проверен.

11.9. Стандартная библиотека D Стандартная библиотека D, фигурирую щ ая в коде под именем P h o b o s 1, органично развивалась вместе с языком. В результате она включает как A P I старого стиля, так и новейшие библиотечные артефакты, исполь­ зующие более современные средства языка.

1 Фобос (Phobos) - больший из двух спутников планеты Марс. «Марс» - изна­ чальное название языка D (см. введение). Digital Mars (Цифровой Марс) компания, разработавшая язык D и эталонную реализацию языка - ком­ пилятор dmd (от Digital Mars D). - Прим. науч.ред.

430 Глава 11. Расширение масштаба Библиотека состоит из двух основных пакетов - соге и std. Первый со­ держ ит фундаментальные средства поддержки времени исполнения:


реализации встроенных типов, сборщик мусора, код для начала и завер­ шения работы, поддержка многопоточности, определения, необходимые для доступа к библиотеке времени исполнения языка С, и другие компо­ ненты, связанные с перечисленными. Пакет std предоставляет функ­ циональность более высокого уровня. Преимущество такого подхода в том, что другие библиотеки можно надстраивать поверх core, а с паке­ том std они будут лиш ь сосуществовать, не требуя его присутствия.

П акет std обладает плоской структурой: большинство модулей распо­ лагаю тся в корне пакета. К аж ды й модуль посвящ ен отдельной функ­ циональной области. И нформация о некоторых наиболее важных моду­ л ях библиотеки Phobos представлена в табл. 11.2.

Таблица 11.2. Обзор ст андарт ных модулей Модуль Описание Этот модуль можно считать основой мощнейшей способности s t d.a l g o r it h m к обобщению, присущей языку. Вдохновлен стандартной биб­ лиотекой шаблонов С++ (Standard Template Library, STL). Со­ держит больше 70 важных алгоритмов, реализованных очень обобщенно. Большинство алгоритмов применяются к струк­ турированным последовательностям идентичных элементов.

В STL базовой абстракцией последовательности служит ите­ ратор, соответствующий примитив D - диапазон, для которо­ го краткого обзора явно недостаточно;

полное введение в диа­ пазоны D доступно в Интернете [3] Функции для удобства работы с массивами s t d.a r r a y Целое число переменной длины с сильно оптимизированной std.b ig in t реализацией Типы и часто используемые функции для низкоуровневых би­ s td.b it m a n ip товых операций Средства параллельных вычислений (см. главу 13) s t d.c o n c u r re n c y Реализации разнообразных контейнеров std.c o n ta in e r Универсальный магазин, удовлетворяющий любые нужды по s t d.c o n v преобразованиям. Здесь определены многие полезные функ­ ции, такие как t o и t e x t Полезные вещи, связанные с датой и временем s t d.d a t e t i m e Файловые утилиты. Зачастую этот модуль манипулирует std.file файлами целиком;

например, в нем есть функция read, кото­ рая считывает весь файл, при этом s t d. f i l e. read и понятия не имеет о том, что можно открывать файл и читать его малень­ кими порциями (об этом заботится модуль s t d. s t d i o, см. далее) Примитивы для определения и композиции функций s td.fu n c tio n a l 11.10. Встроенный ассемблер М одуль О писание С и н такси чески й ан ал и з ком андной строки std.getopt О б раб отка д а н н ы х в ф орм ате JS O N std.json В вы сш ей степени о п ти м и зи р о ван н ы е, часто исп ользуем ы е std.math м атем атические ф ункц ии О бщ ие чи словы е алгори тм ы std.numeric У тилиты д л я м ан и п уляц и й с п утям и к ф ай лам std.path Р азн о о б р азн ы е генераторы с л у ч ай н ы х ч и сел std.random О пределения и п ри м и ти вы к л асси ф и кац и и, им ею щ ие отно­ std.range ш ение к д и ап азо н ам О бработчик р егу л яр н ы х в ы р аж ен и й std.regex С т а н д а р т н ы е б и б л и о т е ч н ы е с р е д с т в а в в о д а /в ы в о д а, п о с т р о е н ­ std.stdio н ы е н а о с н о в е б и б л и о т е к std io я з ы к а С. В х о д н ы е и в ы х о д н ы е и ф ай л ы п редоставляю т и н терф ей сы в сти ле ди ап азон ов, бл аго ­ д а р я ч е м у м н о г и е а л г о р и т м ы, о п р е д е л е н н ы е в м о дstd.algo уле rithm, м о г у т р а б о т а т ь н е п о с р е д с т в е н н о с ф а й л а м и Ф у н к ц и и, сп ец и ф и чн ы е д л я строк. С троки тесно свя зан ы s td.strin g с std.a lg o rith m, т а к ч т о м о д у л ьs t d. s t r i n g, о т н о с и т е л ь н о н е ­ б о л ь ш о й по р а зм ер у, в о сн о вн о м л и ш ь с с ы л а е т с я (о п р е д е л я я п севдоним ы ) н а части std.algorithm, п р и м е н и м ы е к с т р о к а м К ачества типов и и н тросп екц и я std.traits С р е д с т в а д л я о п р е д е л е н и я н о в ы х т и п о в, т а к и х Tuple как std.typecons Ф ункции д ля м ан ипулирования кодировкам и U TF std.u tf О бъявлен ие ти п а Variant, к о т о р ы й я в л я е т с я к о н т е й н е р о м д л я std.variant х р а н е н и я з н а ч е н и я л ю б о г о т и пVariant - э т о в ы с о к о у р о в н е ­ а.

в ы й union 11.10. Встроенный ассемблер Строго говоря, большую часть задач можно решить, не обращаясь к столь низкоуровневому средству, как встроенный ассемблер, а те немногие за­ дачи, которым без этого не обойтись, мож но написать и скомпилировать отдельно, после чего скомпоновать с вашей программой на D обычным способом. Тем не менее встроенный в D ассемблер - очень мощ ное сред­ ство повышения эффективности кода, и упомянуть его необходимо. К о­ нечно, в рамках одной главы невозможно всеобъемлю щ е описать язык ассемблера, да это и не нуж но - ассемблеру для популярны х платформ 1 О п исани е этой ч а с ти я з ы к а не бы ло вкл ю ч ен о в о р и ги н а л к н и г и, но п о­ скольку эта во зм о ж н о сть п ри сутствует в т ек у щ и х р е а л и з а ц и я х я зы к а, м ы д о б а в и л и е е о п и с а н и е в п е р е в о д.Прим. науч.ред.

432 Глава 11. Расширение масштаба посвящ ено множ ество книг1. Поэтому здесь мы приводим синтаксис и особенности применения встроенного ассемблера D, а описание ис­ пользуемы х инструкций оставим специализированны м изданиям.

К моменту написания данной книги компиляторы языка D существо­ вали для платформ * 8 6 и jc86-64, соответственно синтаксис встроенно­ го ассемблера определен пока только для этих платформ.

11.10.1. Архитектура x И нструкции ассемблера можно встроить в код, разместив их внутри конструкции asm:

asm { naked;

mov ECX, EAX;

movEAX, [ESP+size_t.sizeof*1];

movEBX, [ESP+size_t.sizeof*2];

L1:

mov D, [EBX + ECX - 1];

H mov [EAX + ECX - 1], D ;

H loop L1;

ret;

} Внутри конструкции asm допустимы следую щ ие сущности:

• инструкция ассемблера:

инструкция apr^, - a p r^,., аргл;

• метка:

•метка:

• псевдоинструкция:

псевдоинструкция apr^, *арг2,.., аргп;

• комментарии.

К аж дая инструкция пиш ется в ниж нем регистре. После инструкции через запятую указы ваются аргументы. И нструкция обязательно за­ верш ается точкой с запятой. Несколько инструкций могут распола­ гаться в одной строке. Метка объявляется перед соответствующей ин­ струкцией как идентификатор метки с последую щ им двоеточием. Пе­ реход к метке м ож ет осущ ествляться с помощью оператора goto вне бло­ ка asm, а так ж е с помощью инструкций семейства jmp и c a ll. Аналогично внутри блока asm разреш ается использовать метки, объявленные вне блоков asm. Комментарии в код на ассемблере вносятся так ж е, как и в остальном коде на D, другой синтаксис комментариев недопустим.

1 Н а п р и м е р, е с т ь х о р о ш и й у ч е б н и к д л я в у з о в « A s s e m b le r » В. И. Ю р о в а. Прим. науч.ред.

11.10. Встроенный ассемблер Аргументом инструкции мож ет быть идентификатор, объявленны й вне блока asm, имя регистра, адрес (с применением обычных правил адреса­ ции данной платформы) или литерал соответствующ его типа. А дреса можно записывать так (все эти адреса указы ваю т на одно и то ж е зна­ чение):

mov EDX 5[EAX][EBX];

, mov EDX [EAX+5][EBX];

, mov EDX [ EAX+5+EBX];

, Также разрешается использовать любые константы, известные на эта­ пе компиляции, и идентификаторы, объявленные до блока asm:

int* p = a r r. p t r ;

asm { mov EAX, p[EBP];

// Помещает в EA значение X р.

mov EAX, p;

// То же самое.

mov EAX, [p + 2 * in t. s iz e o f ] ;

/ / Помещает в EA второй X // элемент целочисленного массива.

} Если размер операнда неочевиден, используется префикс тип ptr:

add [EAX], 3;

/ / Размер операнда 3 неочевиден, add [EAX], i n t p tr 3;

/ / Теперь все ясно.

Префикс ptr можно использовать в сочетании с типам и near, far, byte, short, int, word, dword, qword, flo a t, double и real. П рефикс far ptr не ис­ пользуется в плоской модели памяти D. По умолчанию компилятор ис­ пользует byte ptr. Префикс seg возвращ ает номер сегмента адреса:

mov EA seg p[EBP];

X Этот префикс такж е не используется в плоской модели кода.

Также внутри блока asm доступны символы: $, указы ваю щ ий на адрес следующей инструкции, и LOCAL_SIZE, означающ ий количество байт в локальном кадре стека.

Для доступа к полю структуры, класса или объединения следует помес­ тить адрес объекта в регистр и использовать полное имя поля в сочета­ нии с offsetof:

s t r u c t Regs { uint eax, ebx, ecx, edx;

} void pushRegs(Regs* p) asm { push EA ;

X mov EA, p;

X 434 Глава 11. Расширение масштаба / / Помещаем в p.ebx значение EBX mov [EAX+Regs.ebx.offsetof], EBX;

/ / Помещаем в p.ecx значение ECX mov [EAX+Regs.ecx.offsetof], ECX;

/ / Помещаем в p.edx значение EOX mov [EAX+Regs.edx.offsetof], ED ;

X pop EBX;

/ / Помещаем в p.eax значение EAX mov [EAX+Regs.eax.offsetof], EBX;

) } Ассем блер * 8 6 допускает обращ ение к следую щ им регистрам (имена регистров следует указывать заглавными буквами):

AL А А НХ EAX BP EBP ES CS SS DS GS FS BL BH BX EBX SP ESP CR0 CR2 CR3 CR CL CH CX ECX DI EDI DR0 D 1 DR2 DR3 DR6 DR R DD D LH X EDX SI ESI TR3 TR4 TR5 TR6 TR ST ST(0) ST(1) ST(2) ST(3) ST(4) ST(5) ST(6) ST(7) M 0 M1 M 2 M3 M 4 M 5 M 6 M MMMMMMMM XM XM XM XM XM XM XM XM M0 M1 M2 M3 M4 M5 M6 M Ассемблер D вводит следую щ ие псевдоинструкции:

align целочисленное_выражение ;

целочисленное_выражение долж но вычисляться на этапе компиляции, align выравнивает следую щ ую инструкцию по адресу, кратному це лочисленному_выражению, вставляя перед этой инструкцией нуж ное ко­ личество инструкций пор (от N ot OPeration), имеющ их код 0x90.

even;

П севдоинструкция even выравнивает следую щ ую инструкцию по четному адр есу (аналогична align 2). Выравнивание может сильно повлиять на производительность в циклах, где часто выполняется переход по выравниваемому адресу.


naked;

П севдоинструкция naked указывает компилятору не генерировать пролог и эпилог ф ункции. В прологе, как правило, создается новый кадр стека, а в эпилоге размещ ается код возвращения значения. Ис­ пользуя naked, программист долж ен сам позаботиться о получении 11.10. Встроенный ассемблер нуж ны х аргументов и возвращении результирующ его значения в со­ ответствии с применяемым функцией соглаш ением о вызовах.

Также ассемблер D разреш ает вставлять в код непосредственные значе­ ния с помощью псевдоинструкций db, ds, di, dl, df, dd, de, которые соот­ ветствуют типам byte, short, int, long, flo a t, 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, Oxff, 0xe2, 0xf6, 0xc3;

/ / Коротко и ясно.

} Префиксы инструкций, такие как lock, rep, repe, repne, repnz и repz, ука­ зываются как отдельные псевдоинструкции:

asm rep;

movsb;

) Ассемблер D не поддерживает инструкцию pause. Вместо этого следует писать:

гер;

пор;

Для операций с плавающей запятой следует использовать формат с дву­ мя аргументами.

fdiv ST(1);

// Неправильно fmul ST;

// Неправильно fdiv ST,ST(1);

// Правильно fmul ST,ST(0);

// Правильно 11.10.2. Архитектура x86- Архитектура * 8 6-64 является дальнейш им развитием архитектуры x и в большинстве случаев сохраняет обратную совместимость с ней. Рас­ смотрим отличия архитектуры x 8 6-64 от x86.

Регистры общего назначения в x 8 6 -6 4 расширены до 64 бит. И х имена:

RAX, RBX, RCX, RDX, RBP, RSI, RDI, RSP, RIP и RFLAGS, причем RIP теперь доступен 436 Глава 11. Расширение масштаба из ассемблерного кода. Вдобавок добавились восемь 64-разрядны х ре­ гистров общ его назначения R8, R9, R10, R11, R12, R13, R14, R15.

Для доступа к м ладш им 32 битам такого регистра к названию добавляется суф­ фикс D, к младш им 16 - W, к младш им 8 - В. Так, R8D - младш ие 4 байта регистра R8, а R15B - младш ий байт R15. Т акж е добавились восемь XMM регистров XMM8-XMM15.

Рассмотрим регистр R IP подробнее. Регистр RIP всегда содержит указа­ тель на следую щ ую инструкцию. Если в архитектуре * 8 6, чтобы полу­ чить адрес следую щ ей инструкции, приходилось писать код вида:

asm { call $;

// Поместить в стек адрес следующейинструкции // и передать на нее управление, pop EBX;

// Вытолкнуть адрес возврата в EBX.

add EBX, 6;

// Скорректировать адрес на размер // инструкций pop, add и mov.

m AL, [EBX];

ov // Теперь AL содержит код инструкции пор;

пор;

} то в * 8 6 -6 4 мож но просто написать1:

asm { m AL, [RIP];

ov / / Загружаем код следующей инструкции, пор;

} К сож алению, выполнить переход по содерж ащ емуся в RIP адресу с по­ мощью jm p / j x x или c a l l нельзя, равно как нельзя получить значение RIP, скопировав его в регистр общего назначения или стек. Впрочем, c a l l $;

как раз помещ ает в стек адрес следую щ ей инструкции, что, по сути, идентично p u s h R IP;

(если бы такая инструкция была допустима). По­ дробную информацию мож но найти в официальном руководстве по конкретному процессору.

11.10.3. Разделение на версии По своей природе ассемблерный код является платформозависимым.

Д ля * 8 6 нуж ен один код, для *86-64 - другой, для SPARC - третий, а компилятор для виртуальной машины вообще может не иметь встроен­ 1 Ассемблер dmd2.052 не поддерживает доступ к регистру RIP. Возможно, данная функция появится позже. Ну а пока вместо mov AL, [RIP];

выможете написатьмантруйЬ 0x8A, 0x05;

di 0х00000000;

,темсамымсообщивсвоеже лание на языке процессора. Помните: если транслятор не понимает некото­ рые символы или инструкции, вы можете транслировать ассемблерный код в машинный сторонним транслятором и вставить в свой ассемблерный код числовое представление команды, воспользовавшись псевдоинструкциями семейства db. - Прим. науч.ред.

11.10. Встроенный ассемблер ного ассемблера. Хорош ая практика — реализовать требуемую ф унк­ циональность без использования ассемблера, добавив альтернативные реализации, оптимизированные для конкретны х архитектур. Здесь пригодится механизм версий.

Компилятор dm определяет версию D_InlineAsm_X86, если доступен ас­ d семблер xS6, и D_InlineAsm_X86_64 если доступен ассемблер * 8 6 -6 4.

Вот пример такого кода:

void optimizedFunction(void* a r g ) { version(D_InlineAsm_X86) { asm { naked;

mov EBX, [EAX];

else version(D_InlineAsm_X86_64) { asm { naked;

mov RBX, [RAX];

} } e ls e { size_t s = *cast(size_t*)arg;

} } 11.10.4. Соглашения о вызовах Все современные парадигмы программирования основаны на процедур­ ной модели. Каким бы ни был ваш код - функциональным, объектно­ ориентированным, агентно-ориентированным, многопоточным, распре­ деленным, - он все равно будет вызывать процедуры. Разум еется, с по­ вышением уровня абстракции, добавлением новых концепций процесс вызова процедур неизбеж но услож няется.

Процедурный подход выгоден при организации взаимодействия фраг­ ментов программы, написанны х на разны х язы ках. Во-первых, разны е языки поддерживают разные парадигмы программирования, а во-вто рых, даж е одни и те ж е парадигмы могут быть реализованы по-разно му. М ежду тем процедурный подход является тем самым фундаментом, на котором основано все остальное. Этот фундамент надеж ен, стандар­ тизирован и проверен временем.

Вызов процедуры, как правило, состоит из следую щ их операций:

• передача аргументов;

• сохранение адреса возврата;

• переход по адресу процедуры;

• выполнение процедуры;

438 Глава 11. Расширение масштаба • передача возвращаемого значения;

• переход по сохраненному адресу возврата.

В высокоуровневом коде знать порядок выполнения этих операций не­ обязательно, однако при написании кода на ассемблере их придется реализовывать самостоятельно.

То, как именно выполняются эти действия, определяется соглашения­ ми о вызовах процедур. И х относительно немного, они хорошо стандар­ тизированы. Разные языки используют разные соглашения о вызовах, но, как правило, допускаю т возможность использовать несколько со­ глаш ений. Соглашения о вызовах определяют, как передаются аргу­ менты (через стек, через регистры, через общ ую память), порядок пере­ дачи аргументов, значение каких регистров следует сохранять, как пе­ редавать возвращ аемое значение, кто возвращает указатель стека на исходную позицию (вызывающая или вызываемая процедура). В сле­ дую щ их разделах перечислены основные из этих соглашений.

11.10.4.1. Соглашения о вызовах архитектуры x А рхитектура x 8 6 за долгие годы своего сущ ествования породила мно­ ж ество соглаш ений о вызовах процедур. У каж дого из них есть свои преимущ ества и недостатки. Все они требуют восстановления значений сегментны х регистров.

cded Д анное соглаш ение принято в язы ке С, отсюда и его название (С Decla­ ration). Большинство языков программирования допускают использо­ вание этого соглаш ения, и с его помощью наиболее часто организуется взаимодействие подпрограмм, написанных на разны х языках. В язы­ ке D оно объявляется как функция с атрибутом extern(C). Аргументы передаются через стек в обратном порядке, то есть начиная с последне­ го. П оследним в стек помещается адрес возврата. Значение возвращает­ ся в регистре EAX, если по размеру оно меньше 4 байт, и на вершине сте­ ка, если его размер превышает 4 байта. В этом случае значение в E X A указы вает на него. Если вы используете псевдоинструкцию naked, вам придется обрабатывать переданные аргументы вручную.

extern(C) i n t increm en t(int а) { asm { naked;

mov EAX, [ESP+4];

/ / Помещаем в EA значение а, смещенное на размер X / / указателя (адреса возврата) от вершины стека, inc EAX;

/ / Инкрементируем EAX ret;

/ / Передаем управление вызывающей подпрограмме.

/ / Возвращаемое значение находится в EAX } } Стек восстанавливает вызывающая подпрограмма.

11.10. Встроенный ассемблер pascal Соглашение о вызовах язы ка Паскаль в D объявляется как ф ункция с атрибутом extern(Pascal). Аргументы передаются в прямом порядке, стек восстанавливает вызываемая процедура. Значение возвращ ается через передаваемый неявно первый аргумент.

stdcall Соглашение операционной системы W indow s, используемое в W inA PI.

Объявление: extern(Windows). Аналогично cdecl, но стек восстанавлива­ ет вызываемая подпрограмма.

fastcall Наименее стандартизированное и наиболее производительное соглаш е­ ние о вызовах. Имеет две разновидности - Microsoft fa s tc a ll и Borland fa stc a ll. В первом случае первые два аргумента в прямом порядке пере­ даются через регистры E X и EDX. Остальные аргументы передаю тся че­ C рез стек в обратном порядке. Во втором случае через регистры EAX, E X D и E Xпередаются первые три аргумента в прямом порядке, остальные ар­ C гументы передаются через стек в обратном порядке. В обоих случаях, если размер аргумента больше размера регистра, он передается через стек. Компиляторы D на данный момент не поддерживают данное согла­ шение, однако при использовании динамических библиотек есть воз­ можность получить указатель на такую функцию и вызвать ее с помо­ щью встроенного ассемблера.

thiscall Данное соглашение обеспечивает вызов методов класса в язы ке С++.

Полностью аналогично std c a ll. Указатель на объект, метод которого вызывается, передается через EC.

X Соглашение язы ка D Ф ункция D гарантирует сохранность регистров EBX, ESI, EDI, ЕВР.

Если данная функция имеет постоянное количество аргументов, пере­ менное количество гомогенных аргументов или это шаблонная функ­ ция с переменным количеством аргументов, аргументы передаются в прямом порядке и стек очищ ает вызываемая процедура. (В противном случае аргументы передаются в обратном порядке, после чего передает­ ся аргумент _arguments. _argptr не передается, он вычисляется на базе _arguments. Стек в этом случае очищ ает вызывающая процедура.) После этого в стеке резервируется пространство под возвращ аемое значение, если оно не мож ет быть возвращено через регистр. П оследним передает­ ся аргумент th is, если вызываемая процедура - метод структуры или класса, или указатель на контекст, если вызываемая процедура —дел е­ гат. Последний аргумент передается через регистр EAX, если он умещ а­ ется в регистр, не является трехбайтовой структурой и не относится 440 Глава 11. Расширение масштаба к ти пу с плавающ ей запятой. Аргументы ref и out передаются как ука­ затель, lazy - как делегат.

Возвращ аемое значение передается так:

• bool, byte, ubyte, short, ushort, int, ulnt, 1-, 2- и 4-байтовые структуры, указатели (в том числе на объекты и интерфейсы), ссылки - в EA ;

X • long, ulong, 8-байтовые структуры - в E X(старшая часть) и EA (млад­ D X ш ая часть);

• flo a t, double, real, iflo a t, idouble, ireal - в ST0;

• c flo a t, cdouble, c r e a l - в ST1 (действительная часть) и ST0 (мнимая часть);

• динам ические массивы - в E X (указатель) и EA (длина массива);

D X • ассоциативны е массивы - в EAX;

• делегаты - в E X (указатель на функцию) и EA (указатель на кон­ D X текст).

В остальны х случаях аргументы передаются через скрытый аргумент, размещ енны й на стеке. В EA в этом случае помещается указатель на X этот аргумент.

11.10.4.2. Соглашения о вызовах архитектуры x86- С переходом к архитектуре * 8 6 -6 4 количество соглашений о вызовах су­ щественно сократилось. По сути, осталось только два соглашения о вы­ зовах -M icrosoft x64 callin g conveпtionдляWindowsиAMD64 ABI convention для P osix.

M icrosoft x64 calling convention Это соглаш ение о вызовах очень напоминает fa stc a ll. Аргументы пере­ даю тся в прямом порядке. Первые 4 целочисленных аргумента переда­ ются в RCX, R X, R8, R9. Аргументы размером 16 байт, массивы и строки D передаю тся как указатель. Первые 4 аргумента с плавающей запятой передаю тся через X M X M X M X M При этом место под эти аргумен­ M 0, M 1, M 2, M 3.

ты резервируется в стеке. Остальные аргументы передаются через стек.

Стек очищ ает вызывающая процедура. Возвращ аемое значение переда­ ется в RAX, если оно умещ ается в 8 байт и не является числом с плаваю­ щ ей запятой. Ч исло с плавающ ей запятой возвращается в X M Если M 0.

возвращ аемое значение больше 64 бит, память под него выделяет вызы­ вающ ая процедура, передавая ее как скрытый аргумент. В этом случае в R X возвращ ается указатель на этот аргумент.

A AMD64 ABI convention Д анное соглаш ение о вызовах используется в P osix-совместимых опера­ ционных системах и напоминает предыдущ ее, однако использует боль­ ш е регистров. Д ля передачи целы х чисел и адресов используются реги­ стры RDI, RSI, R X R X R8 и R9, для передачи чисел с плавающей запятой D, C, 11.10. Встроенный ассемблер X M X M X M X M X M X M X M и X M Если требуется передать ар­ M 0, M 1, M 2, M 3, M 4, M 5, M 6 M 7.

гумент больше 64 бит, но не больше 256 бит, он передается по частям через регистры общего назначения. В отличие от Microsoft x64, для пере­ данных в регистрах аргументов место в стеке не резервируется. Возвра­ щаемое значение передается так ж е, как и в Microsoft x64.

11.10.5. Рациональность Решив применить встроенный ассемблер для оптим изации программы, следует понимать цену повышения эффективности. Ассемблерный код трудно отлаживать, ещ е труднее сопровождать. Ассемблерный код об­ ладает плохой переносимостью. Д аж е в пределах одной архитектуры наборы инструкций разны х процессоров несколько различаю тся. Более новый процессор мож ет предлож ить более эффективное реш ение стоя­ щей перед вами задачи. А раз у ж вы добиваетесь максимальной произ­ водительности, то, возможно, предпочтете скомпилировать несколько версий своей программы для различны х целевых архитектур, напри­ мер одну переносимую версию, использую щ ую только инструкции из набора i386, другую - для процессоров AM D, третью - для Intel Core.

Для обычного высокоуровневого кода достаточно просто указать соот­ ветствующий флаг ком пиляции1, а вот в случае с ассемблером придет­ ся создавать несколько версий кода, делаю щ их одно и то ж е, но разны ­ ми способами.

version(AMD) { version = i686;

version = i386;

} else version(iC ore) { version = i686;

version = i386;

} e ls e version(i686) { version = i386;

1 Компилятор dmd2.057 пока трудно назвать промышленным компилято­ ром, поэтому упомянутого механизма в нем пока нет, а вот компилятор язы­ ка С gcc предоставляет возможность указать целевую платформу. Это позво­ ляет получить максимально эффективный машинный код для данной платформы, при этом в код на языке С вносить изменения не нужно. Чита­ телям, нуждающимся в компиляторах D, способных генерировать более оп­ тимизированный код, следует обратить внимание на проекты GDC (GNU D compiler) и LDC (LLVM D compiler) компиляторов D, построенных на базе генераторов кода GCC и LLVM. - Прим. науч. ред.

442 Глава 11. Расширение масштаба void f a s tP ro c e ss() { version(AMD) { // } e l s e version(iC ore) { // } e ls e v ersion(i686) { // } e l s e v ersion(i386) { // } else { // } И все это ради того, чтобы выжать из функции fastProcess максимум производительности! Тут-то и надо задаться вопросом: а в самом ли деле эта ф ункция является краеугольным камнем вашей программы? Мо­ ж ет быть, ваша программа недостаточно производительна из-за ошиб­ ки на этапе проектирования, и выбор другого решения позволит сэконо­ мить секунды процессорного времени - против долей миллисекунд, сэкономленных на оптим изации fastProcess? А может, время и, как следствие, деньги, которых требует написание ассемблерного кода, луч­ ш е направить на повышение производительности целевой машины?

В любом случае задействовать встроенный ассемблер для повышения производительности нуж н о в последнюю очередь, когда остальные средства у ж е испробованы.

Перегрузка операторов Мы, программисты, не очень любим слиш ком у ж отделять встроенные типы от пользовательских. М агические свойства встроенны х типов ме­ шают открытости и расширяемости любого язы ка, поскольку при этом пользовательские типы обречены оставаться второсортными. Тем не менее у проектировщиков язы ка есть все законны е основания отно­ ситься к встроенным типам с большим почтением. Одно из так их осно­ ваний: более настраиваемый язы к слож нее выучить, а так ж е слож нее выполнять его синтаксический анализ как человеку, так и маш ине. Ка­ жды й язык по-своему определяет приемлемое соотнош ение м еж ду встроенным и настраиваемым, что для некоторых языков означает впа­ дение в одну из этих двух крайностей.

Язык D подходит к этому вопросу прагматично: он не ум аляет важ ­ ность настраиваемости, но при этом осознает практичность встроенных типов - D использует преимущ ества встроенных типов ровно тремя пу­ тями:

1. Синтаксис названий типов. Массивы и ассоциативные массивы ис­ пользуются повсеместно, и, согласитесь, синтаксис i n t [ ] и i n t [ s t r i n g ] гораздонагляднее, 4 e M A r r a y ! i n t H A s s o t i a t i v e A r r a y ! ( s t r i n g, i n t ). B r o x n b зовательском коде нельзя определять новые формы записи названий типов, например i n t [ [ ] ].

Лит ералы. Числовые и строковые литералы, как и литералы масси­ вов и ассоциативных массивов, —«особые», и их набор нельзя расш и­ рить. «Сборные» объекты-структуры, такие как Point(5, 3), - тож е литералы, но тип не мож ет определить новый синтаксис литерала, например (3, 5)pt.

Семантика. Зная семантику определенных типов и их операций, компилятор оптимизирует код. Например, встретив вы ражение "Hello" ^ " " ^ "world ', компилятор не откладывает конкатенацию до 444 Глава 12. Перегрузка операторов времени исполнения: он знает, что делает операция конкатенации строк, и склеивает строки уж е во время компиляции. Аналогично компилятор упрощ ает и оптимизирует арифметические выражения, используя знание арифметики.

Некоторые язы ки добавляют к этому списку операторы. Они делают операторы особенными;

чтобы выполнить какую-либо операцию приме­ нительно к пользовательским типам, приходится использовать стан­ дартные средства язы ка, такие как вызов функций или макросов. Не­ смотря на то что это совершено законное решение, оно на самом деле создает проблемы при большом объеме кода, ориентированного на ариф­ метические вычисления. Многие программы, ориентированные на вы­ числения, определяют собственные типы с алгебрами1 (числа неограни­ ченной точности, специализированные числа с плавающей запятой, кватернионы, октавы, матрицы всевозможных форм, тензоры,... оче­ видно, что язы к не мож ет сделать встроенными их все). При использова­ нии таких типов выразительность кода резко сниж ается. По сравнению с эквивалентным функциональным синтаксисом, операторы обычно требуют меньше места и круглых скобок, а получаемый с их участием код зачастую легок для восприятия. Рассмотрим для примера вычисле­ ние среднего гармонического трех ненулевых чисел x, у и z. Выражение на основе операторов очень близко к математическому определению:

m = 3 / (1/х + 1/у + 1/z);



Pages:     | 1 |   ...   | 10 | 11 || 13 | 14 |   ...   | 15 |
 





 
© 2013 www.libed.ru - «Бесплатная библиотека научно-практических конференций»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.