Язык D спешит на помощь. V595. Неправильно проверенный указатель. Может быть очень сложно обучиться программированию и преподавать его

Лозунги "надо писать программы без ошибок и тогда не будут нужны вспомогательные инструменты" не имеют смысла. Мы все делали, делаем и будем делать ошибки при программировании. Конечно, нужно улучшать стиль кодирования, делать обзоры кода, использовать различные методологии, которые снизят количество ошибок. Однако ошибки всё равно будут. И если компилятор поможет выявить хотя бы часть из них, это замечательно. Именно поэтому я считаю отличной идей, вводить специальные механизмы в язык программирования, которые помогут избежать ошибок. В языке D этому уделяется существенное внимание и это замечательно.

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

Каковы ошибки, возникающие из-за невнимательности? Сложный вопрос. Однако у меня есть кое-что, что позволит правдоподобно ответить на него. Испытывая анализатор PVS-Studio, мы анализируем различные open-source проекты. Найденные ошибки мы помещаем в базу данных. И чем больше ошибок определенного типа мы находим, тем больше примеров появляется у соответствующей диагностики. Конечно, анализатор ищет не все возможные типы ошибок. Однако диагностик уже много, чтобы выявлять закономерности.

Просто так нельзя взять и посмотреть, какие диагностики обнаружили максимальное количество ошибок. Новые диагностические правила появляются постепенно. Поэтому, чем раньше была реализована диагностика, тем большее проектов было проверено с её помощью. Я объединил схожие диагностики, ввёл поправочный конфидент и произвёл некоторые расчёты. Не буду утруждать читателя подробностями. Просто приведу список из 10 диагностик, которые выявляют максимальное количество ошибок: V547 +V560 , V595 , V501 , V512 +V579 , V557 , V567 +V610 , V597 , V519 , V576 , V530 . По приведённым ссылкам можно увидеть примеры обнаруженных ошибок. Можно сказать, что это "top 10" типовых ошибок, которые делают программисты на языке Си/Си++.

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

  • Есть ли какие-то механизмы в языке D, которые помогут защитить программиста от таких ошибок?
  • Если нет, то планируется ли что-то для этого сделать и как это может выглядеть?
  • Какие можно дать рекомендации программистам, использующих Си++ или D, чтобы избегать таких ошибок?

1. V547/V560. Всегда ложные или истинные условия

Причин когда возникает бессмысленное условие множество. Это может быть опечатка, неаккуратный рефакторинг, неправильный тип. Компилятор Си/Си++ лояльно относится к условиям, которые всегда истинны или ложны. Иногда такие выражения полезны. Например, можно встретить подобный код:

#define DEBUG_ON 0 if (DEBUG_ON && Foo()) Dump(X);

Однако, от этой возможности очень много вреда (примеры , примеры) . Вот один из типовых примеров:

Std::string::size_type pos = dtaFileName.rfind("/"); if (pos < 0) { pos = dtaFileName.rfind("\\"); }

Переменная "pos" имеет без знаковый тип. Поэтому условие (pos < 0) всегда ложно.

Комментарий Уолтера:

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

Конструкция unsigned<0 обычно является ошибкой в том случае, если она существует в коде высшего уровня. Однако она вполне допустима в обобщённом коде в качестве граничного случая. Она также может использоваться в коде для проверки на тип unsigned, как в случае с in -(T)1<0. Так что я бы не стал однозначно называть такой код всегда ошибочным.

2. V595. Неправильно проверенный указатель

Зачем проверяют указатель на равенство нулю, объяснять не надо. Но не многие догадываются, как хрупок этот код. Очень часто в процессе неаккуратного рефакторинга, указатель начинают использовать до проверки (примеры). Например, подобная ошибка может выглядеть так:

Buf = buf->next; pos = buf->pos; if(!buf) return -1;

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

Комментарий Уолтера:

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

3. V501. Опечатки и Code-Paste

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

If(m_GamePad.wButtons || m_GamePad.sThumbLX || m_GamePad.sThumbLX || m_GamePad.sThumbRX || m_GamePad.sThumbRY || m_GamePad.bLeftTrigger || m_GamePad.bRightTrigger)

В подобном коде так соблазнительно скопировать строчку и немного поправить её. Результатом такого желания, будет странное поведение программы при специфическом стечении обстоятельств. Если читатель не заметил ошибку, то подскажу. Два раза проверяется член класса "sThumbLX", но нет проверки для "sThumbLY"

Комментарий Уолтера:

Я изучал этот вопрос для реализации в D. Но проблема в том, чтобы проверить, не имеет ли дублированное условие побочных эффектов и не имеют ли какие-нибудь условия между копиями побочных эффектов, которые могут повлиять на результат дублированного условия. Чтобы проверка работала надёжно и не давала ложных срабатываний, требуется качественный анализ потока данных.

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

4. V512/V579. Обработка только части буфера

Типовой ошибкой при использовании таких функций как memset, memcpy, strncmp, является обработка только части буфера. Ошибка возникает, когда вместо размера буфера вычисляется размер указателя. Кажется, такая ошибка должна сразу быть выявлена. Однако такие ошибки живут в программах многие годы (примеры , примеры). Например, приведённый ниже код для проверки целостности таблицы почти работает.

Const char * keyword; .... if (strncmp(piece, keyword, sizeof(keyword)) != 0) { HUNSPELL_WARNING(stderr, "error: line %d: table is corrupt\n", af->getlinenum());

Сравнивается только часть ключевого слова. А именно 4 или 8 байт, в зависимости от размера указателя на данной платформе.

Комментарий Уолтера:

Эти несчастные ошибки очень хорошо изгоняются из кода, если придерживаться синтаксиса языка D в массивах:

If (piece.startsWith(keyword)) ...

Вряд ли вы увидите функции memset, strncmp и т.д. в D коде. Массивы в D знают свои размеры, поэтому ошибки с неправильной длиной массива, характерные для C, остались в прошлом. По моему не очень скромному мнению, самая большая ошибка C заключалась в отделении длины от массивов при их передаче в функции, а вторая самая большая ошибка - использование нуль-терминальных строк. В D исправлены оба этих недочёта.

5. V557. Выход за границы массива

Классика программистского жанра. Способов совершить такие ошибки огромное множество:

  • Ошибка в алгоритме вычисления индекса.
  • Можно ненадолго забыть, что элементы в массиве нумеруются с 0.
  • Неаккуратный рефакторинг (размер массива изменили, а цикл - нет).
  • Опечатка или Copy-Paste.

Если посмотреть , то можно подобрать пример к каждому из перечисленных пунктов. Приведу только один самый простой пример:

#define FINDBUFFLEN 64 // Max buffer find/replace size static char findWhat = {"\0"}; .... findWhat = "\0";

Комментарий Уолтера:

Данная проблема во многом похожа на пункт (4). Массивы в D реальны и знают собственную длину. Проверка границ массива производится по умолчанию (но её по желанию можно отключить с помощью ключа командной строки). Проблема выхода за границы массива теперь покоится на свалке истории. Конечно в D всё так же можно использовать сырые указатели и производить над ними арифметические действия, но использоваться подобным образом они должны лишь изредка и не являться нормой, а опыт показывает, что сырые указатели следует преобразовывать в массивы как можно скорее.

6. V567/V610. Неопределённое поведение программы

Этот тип ошибки является наиболее дискуссионным. Хотя стандарт четко говорит, что какая-то конструкция приведёт к неопределённому поведению , разработчики часто с этим не соглашаются. Их аргументы:

  • Опасный код у них работал больше 10 лет и продолжает работать.
  • Да, здесь потенциальная ошибка. Но любой нормальный компилятор поймет, что я имел ввиду, и всё будет хорошо.
  • Я не буду менять платформу и компилятор. Значит всё и дальше будет работать хорошо.

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

Разнообразные примеры можно посмотреть и . Приведу пару опасных конструкций:

M_Quant.IQuant = m_Quant.IQuant--; intptr_t Val = -1; Val <<= PointerLikeTypeTraits::NumLowBitsAvailable;

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

У любого вменяемого программиста который сталкивается с языком программирования С++ должно рано или поздно возникать чувство отвращения и полной богозабытости. И если в случае с Java кто-то еще может поспорить что “светлое будущее впереди” (фиаско с Java 7 как бы намекает), то с С++ мне кажется определились все: и те кто недавно перешли или решили просто попробовать, и те кто с ним работают десятилетиями.

С++ устарел. Программы на С++, как бы красиво они не выглядели, не соответствуют современным реалиям. Попытки пропатчить C++ под 0x/2011 выглядят трезвыми только на фоне того, что в “плюсах” появились лямбда-выражения, а в Java 7 у Oracle нехватило силенок их сделать.

Вообще, мне по большому счету все равно. Все равно насчет Java-стека т.к. на Java я не пишу, на Scala меня не тянет (синтаксис F# на порядок лучше, имхо). С другой стороны, у меня есть небольшие “вкрапления” С++ в проекты, поэтому о них наверное стоит рассказать.

Зачем нужен С++?

Конкретно мне С++ нужен для параллелизации. Нет, поймите меня правильно, я ни в коей случае не против TPL и всяких прелестей вроде Parallel.For или AsParallel() . Более того, все что я сейчас делаю с коллекциями обычно содержит использование TPL т.к. это достаточно безопасный способ ускорить программу.

Но есть ряд областей где.Net-а мало. Возьмем например обработку изображений - даже если вы напишете обработку Bitmap’а в unsafe режиме на C#, это все равно будет существенно медленнее чем использование C++. Особенно если учесть, что в плюсах есть доступ к SSE (__mm_mul_ps и все такое). И самое главное, что манипуляций-то никаких особо не нужно, т.к. класс System.Drawing.Bitmap может выдать указатель на фиксированный блок памяти, который в последствии можно через P/Invoke передать в тело метода на C++. А дальше можно использовать все прелести OpenMP, Threading Building Blocks, а также мощный инструментарий.

Кстати о… думаю стоит пояснить, что я религиозно использую стек Intel , т.е. Intel Parallel Studio со всеми прелестями. Это важно потому что инструментарий этот достаточно хороший и много чего умеет, компилятор С++ от Microsoft в былые годы (до Intel) был ооочень слабоват - в те времена, чтобы заработали примеры из Generative Programming , нужно было использовать KAI C++, Metrowerks или еще какой-то сторонний компилятор. Мне правда повезло - на шаблонное метапрограммирование я так и не подсел, хотя идея конечно заманчива.

Итак, если прорезюмировать то, зачем мне С++ нужен, должен сказать, что нужен он для производительности в параллелизуемых алгоритмах. Для нативных библиотек С++ не критичен, т.к. всегда можно написать P/Invoke обертку или скачать уже готовую, а вот для новых, свежих алгоритмов - при условии что они не требуют качественного библиотечного окружения - С++ очень даже подходит.

А причем тут D?

Я уже давно хотел попробовать D, но все нехаватало времени. Теперь же у меня получилось истратить день на изучение особенностей языка и выборочное читание частей The D Programming Language Александреску . Книга, кстати, классная - объем информации большой, написано с долей весьма своеобразного юмора.

Собственно использование D под Windows требует от вас две вещи.

Во-первых, нужно скачать компилятор. Инсталлятор (MSI) все делает сам, напрягаться толком не нужно.

Во-вторых, хоть это и опционально, можно скачать IDE или пакет поддержки уже существующей IDE. Я конечно же скачал - пакет для Visual Studio. Особенности пакета:

    Позволяет создавать проекты на языке D и конфигурировать их

    Весьма слабо поддерживает IntelliSense - можно сказать что поддержки практически нет

    Есть отладка (YMMV, конечно же)

    Все это, увы, 32-bit

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

D vs. C++

В чем же приемущества D? Попробую перечислить то, что бросилось в глаза:

    Нет этого бреда с заголовочными файлами, #pragma once и т.п. Порядок файлов в проекте тоже не важен (F#, я смотрю на тебя). Пишешь module foo в одном файле и import foo в другом.

    Строки… строки вменяемы, почти как в C#. Почти потому что остались извраты с UTF-16 и UTF-32 для тех кто знает в этом толк. Да, естественно что до.Net в плане глобализации не дотянуть - но цель не в этом.

    Сборка мусора. При этом можно выделять память собственноручно если вдруг у вас приступ мазахизма.

    Свойства! Java и С++ могут убить себя об стену, __declspec(property) идет лесом. Свойства, имхо, должны быть в любом современном языке программирования. И тут они есть, и ими приятно пользоваться.

    Piece de resistance: ключевое слово mixin . Самый вменяемый подход к метапрограммированию, который просто ставит в угол Boo, Nemerle и иже с ними.

Язык D отличается очень продуманным, вменяемым дизайном и при этом не несет с собой кучу устаревшей/спорной идеологии на тему того что, например, публичные поля это плохо, надо делать свойства.

Mixin

Как работает метапрограммирование в языках вроде Boo? Там с помощью цитирования создается объектная модель кода - например [| x + y |] превращается в BinaryExpression . Вставки из реального мира делаются с помощью splice-оператора $ . Тем самым, чтобы хоть что-то сгенерировать, нужно уметь создавать объектную модель нужного кода.

В большинстве случаев это вообще не нужно. Гораздо проще взять и сгенерировать строку (на подобии T4), которая создает текстовое представление и добавляет его в исходный код на этапе компиляции. Это в 100 раз проще чем мучаться с объектной моделью компилятора.

В языке D, инструкция mixin просто принимает строку. Напишите mixin("string Name;") и вы добавили в класс поле Name . Тривиально. Естественно что строки можно не только передавать напрямую, но и создавать отдельные функции, которые исполняются на этапе компиляции . Это решает проблему метапрограммирования в большинстве случаев.

Недостатки

Куда же без них. Вот то, что я успел заметить:

    Дебаггер порой идет совсем не туда куда нужно. Вложенные функции и делегаты дебажатся “с грехом пополам”.

    Конкретно под Windows, D компилируется только в 32-bit.

    Стандартные библиотеки конечно не дотягивают до какой-нть STL но они намного более вменяемы. Так что это палка о двух концах.

    Стандартных библиотек на самом деле две - Phobos и Tango. Какую использовать - непонятно. В Phobos я уже столкнулся с недопиленностью - например std.xml сейчас переписывается.

    Никто пока не воспринимает D серьезно. Даже у F# больше exposure чем у D, что собственно понятно - ведь F# продвигает Microsoft и этот язык активно используется в quant finance.

    IntelliSense не работает в Visual D. Надо будет еще глянуть на D-IDE которая, кстати, написана под.Net 4:)

Впечатления

Мне D очень понравился . Это язык с максимальным уровнем вменяемости и достаточно низким уровнем шума. Если вам не нужно завязок на 100 библиотек, D позволит вам быстро разрабатывать и компилировать ваш код. При этом вы не теряете в выразительности - тут и делегаты, и события, и вложенные функции, функциональные литералы (= лямбда-выражения), не говоря уже о совсем продвинутых вещах вроде вариадичных шаблонов.

Резюмируя, должен сказать что D меня очаровал, и в ближайшее время я планирую его использовать к некритичных, non-production проектах дабы прочувствовать все его возможности. А они, судя по первичным наблюдениям, весьма обширны.

Он является языком высокого уровня , но сохраняет возможности прямого взаимодействия с программным интерфейсом операционной системы и с оборудованием. D предназначен для написания средних и крупных систем с миллионами строк исходного кода , для ведения командной разработки. Язык D имеет C-подобный синтаксис , он лёгок в изучении, поддерживает многие возможности в помощь программисту, а также пригоден для проведения агрессивной оптимизации кода компилятором.

Стабильная версия 1.0 работает на Windows , Linux , Mac OS , а с версии 1.043 также и на FreeBSD . Так же с недавнего времени стали доступны исходные коды DMD (официальная реализация компилятора от Digital Mars).

Основные особенности D

  • Облегчает написание кросс-платформенного кода;
  • Лёгкость изучения языка для тех, кто имел дело с языками C и C++;
  • Обеспечение прямого низкоуровневого доступа к оборудованию ;
  • Наличие контекстно-независимой грамматики ;
  • Легкость создания интернациональных программ ;
  • Возможность создания легковесных и самостоятельных программ

Возможности, унаследованные от C++

Синтаксис языка D схож с синтаксисом C++, что облегчает его изучение людям, знакомым с С++, а также перенос исходного кода с С++.

Отсутствие макросов

Отказ создателей языка от препроцессора (как например в языке Cи) многие расценивают как рискованный и неверный шаг. Но в Ди имеются встроенные средства, которые позволяют обходиться без препроцессора.

Данный пост начинает серию переводов D Programming Language Tutorial , дабы компенсировать информационный вакуум об этом системном языке. Каждая часть будет содержать константое количество материала из книги, так как оригинальные главы имеют широкий разброс в размере: от пары абзацев до нескольких печатных страниц. Все примеры кода проверяются на текущем релизе компилятора dmd 2.065 , и если возникают проблемы с технической частью, прошу отписываться в комментариях.

Благодарности

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

Главы прошли первоначальную проверку членами турецкого форума Ddili Forum . Я благодарен турецкому коммьюнити D за поддержание моего азарта и мотивации на высоком уровне.

Mert Ataol, Zafer Çelenk, и Salih Dinçer написали отзывы практически на каждую строчку книги. Can Alpay Çiftçi и Faruk Erdem Öncel играли важную роль в развитии и книги и ddili.org.

Спасибо следующим людям за их существенные исправления, рекомендации и идеи: Ergin Güney, Jordi Sayol, David Herberth, Andre Tampubolon, Gour-Gadadhara Dasa, Raphaël Jakse, Andrej Mitrovic, Johannes Pfau, Jerome Sniatecki, Jason Adams, Ali H. Çalışkan, Paul Jurczak, Brian Rogoff, Михаил Страшун, Joseph Rushton Wakeling, Tove, Hugo Florentino, Satya Pothamsetti, Luís Marques, Christoph Wendler.

Эта книга была вычитана Ergin Güney для улучшения моего Инглиша (ориг. Inglish) до нормального Английского.

Введение

Книга предназначена для обучения языку D читателей - новичков в сфере программирования. Хотя наличие опыта в других языках программирования несомненно было бы полезным, эта книга начинает с самых основ. Если вы заинтерисованы научиться программировать, я надеюсь, что найдете книгу полезной.

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

  • текстовый редактор
  • компилятор для D

Вместо того, чтобы скачивать и устанавливать их по отдельности, можно рассмотреть вариант с интегрированной средой разработки (IDE). Вы можете найти информацию о редакторах и IDE для D на страницах Editors и IDE на dlang.org. Обучиться программировать на D (прим. да вообще на любом языке, кроме псевдокода) без текстового редактора или комплилятора невозможно. Инструкции по установке компилятора dmd и как им пользоваться будут рассмотрены чуть позже в следующих главах.

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

Я предполагаю, что вы не пропускаете главы. Если натыкаетесь на главы, которые особенно тяжело даются, это может быть из-за того, что в книге неудачно предоставлены все необходимые понятия. Пожалуйста, напишите автору ([email protected]) (прим. оставьте комментарий) о таких проблемах, чтобы помочь сделать эту книгу полезнее.

Книга не раскрывает программирование с использованием графического интерфейса пользователя (GUI). Хотя многие программы гораздо удобнее использовать вместе с GUI, GUI напрямую не относится к языкам программирования. Более того, проектные решения и стиль программирования с GUI может конфликтовать со стилем самого языка и его стандартной библиотеки, и усложнить изучение языка. Поэтому книга описывает только консольные программы. Как только изучены основы D и его стандартная билиботека, Phobos, вы сможете использовать любую библиотеку для GUI, какую захотите.

Главы книги доступны онлайн по мере перевода с Турецкого языка. Можно использовать ленту RSS , чтобы быть в курсе выхода новых глав.

Изучать программирование гораздо веселее в коллективе. Заходите на D.learn newsgroup , чтобы следить за дискуссиями и задавать вопросы, и отвечать на них.

Практика программирования

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

  • Это задача создания программ, которые заставляют компьютер вести себя ожидаемым образом
  • Так как оно требует инструменты и использование этих инструментов руководствуется опытом программистов-мастеров, оно является ремеслом
  • Так как оно включает в себя решение проблем с ограничениями, оно является инженерным искусством
  • Оно очень увлекательное и приносит удовлетворение
  • Оно не является изобразительным искусством, но насколько это возможно в любой человеческой деятельности, программы могут являться произведениями искусства
  • Оно не является наукой, но используемые в нем методы созданы наукой-информатикой.

Может быть очень сложно обучиться программированию и преподавать его

Программирование преподается с 1950-х, и все еще не разработаны эффективные или успешные обучающие методики.

К сожалению, обучение программированию можеть быть сложной задачей для примерно половины из всех студентов. Согласно научной статье (прим. ориг. ссылка нерабочая), те, кто изучают программирование с легкостью, являются теми, кто способны создавать модели консистентности для описания новых ситуаций, с которыми сталкиваются.

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

The Hello World

Первой программой в большинстве книг по программированию является hello world программой. Это очень короткая и простая программа, которая выводит «привет мир» и завершается. Эта программа важна, так как она включает некоторые базовые понятия языка.

Ниже hello world на D:

import std.stdio ;

void main()
{
writeln("Hello world!" ) ;
}

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

Установка компилятора

На момент написания этой главы можно выбирать из трех компиляторов D: dmd , компилятор от Digital Mars; gdc , компилятор D для GCC; и ldc , компилятор, который использует LLVM инфраструктуру.

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

Для установки последней версии dmd зайдите на страницу скачивания на Digital Mars и выберите версию компилятора, которая подходит под окружение компьютера. Вы должны выбрать версию dmd , которая соответствует установленной операционной системе и пакетному менеджеру, и совпадает с архитектурой процессора: 32-разрядная или 64-разрядная. Не устанавливаете компилятор для D1 (прим. первая версия - уже история)! Эта книга освещает только D второй версии .

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

Исходник

Файл, который пишет программист для компиляции, называется файлом исходных кодов или просто исходник. Так как D - компилируемый язык, исходники сами по себе не являются исполняемыми файлами. Исходный код должен быть переведен в исполняемую программу компилятором.

Как и все файлы, исходник должен иметь имя. Хотя это имя может любым, которое дозволяется операционной системой, обычно используется расширение .d для файлов с исходными кодами на D, так как среды разработки, инструменты программирования и программисты ожидают именно такое расширение. Например, test.d , game.d , invoice.d и т.д. подходят как имена для исходника.

Компилирование hello world

Скопируйте текст программы выше в текстовый файл и сохраните под именем hello.d .

Компилятор скоро проверит корректность синтаксиса исходного кода (т.е. на соответствие правилам языка) и создаст из него программу переводом в машинные коды. Для компиляции следуйте следущим шагам:

  1. Откройте окно консоли
  2. Перейдите к директории, где сохранен hello.d
  3. Введите следующую комманду (Не пишите символ $ , он для обозначения командной строки.) $ dmd hello.d

Если вы не совершили ошибок, то может показаться, что ничего не произошло. Иначе, это означает, что все прошло хорошо. В папке должен появится исполняемый файл с именем hello (или hello.exe под Windows), который только что был создан компилятором.

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

Как только программа удачно создалась, напишите имя исполняемого файла для ее запуска. Программа должна вывести «Hello world!»:

$ ./hello ← запуск программы Hello world! ← сообщение, которое она вывела

(прим. под Windows вместо./hello нужно вводить hello)

Поздравляю! Ваша первая программа на D работает как ожидается.

Флаги компилятора

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

$ dmd ← введите только имя DMD64 D Compiler v2.065 Copyright (c) 1999-2013 by Digital Mars written by Walter Bright Documentation: http://www.dlang.org/index.html Usage: dmd files.d ... { -switch } files.d D source files ... -unittest compile in unit tests ... -w enable warnings ...

Сокращенный вывод выше показывает только флаги, которые я рекомендую всегда использовать. Хотя это не имеет значения для hello world программы в этой главе, следующая комманда скомпилирует программу с включенными предупреждениями и модульными тестами . Мы рассмотрим эти и другие параметры детальнее в следующих главах:

$ dmd hello.d -w -unittest

Полный список параметров dmd можно найти в оффициальной документации DMD .

Еще один флаг, который может быть полезен: -run . Он компилирует исходный код, создает исполняемый файл, и запускает его за одну комманду:

$ dmd -run hello.d -w -unittest Hello world! ← программа автоматически запустилась

IDE

В добавок к компилятору можно установить IDE (интегрированная среда разработки). IDE спроектированы для упрощения разработки программ путем упрощения шагов написания, компиляции и отладки кода.

Если вы установите IDE, компиляция и запуск программы будут проводиться просто нажатием на клавишу клавиатуры или кнопку в IDE. Я все еще рекомендую ознакомиться с компилированием программ вручную в консольном окне.

Если же решили установить IDE, перейдите на страницу с IDE на dlang.org , чтобы увидеть список доступных IDE (прим. пер. я пользуюсь DDT).

Разбор hello world программы

Вот краткий список из многих понятий из D, которые появились в этой короткой программе:

Ядро: Каждый язык определяет свой синтаксис, фундаментальные типы, ключевые слова, правила и т.п. Все они формируют ядро этого языка. Круглые скобки, точки с запятой и слова, такие как: main и void - все в соответствии с правилами D. Это похоже на правила Английского (Русского) языка: подлежащие, глаголы, пунктуация, структура предложения и т.д.

Ключевое слово: Специальные слова, которые являются частью ядра языка называются ключевыми . В этой программе есть два ключевых слова: import , которое используется для подключения модулей к программе; и void , которое означает «ничего не возвращает».

Библиотека и функция: Ядро определяет только структуру языка. Оно используются для определения функций и пользовательских типов, и они в свою очередь используются для проектирования библиотек. Библиотека - коллекция многоразовых частей программ, которые связываются (прим. линкуются) с вашими программами для помощи в достижении их целей.

writeln выше является функцией в стандартной библиотеке D. Она используется для вывода строки текста, как можно догадаться по ее имени: write line - написать строку.

Модуль: Содержимое библиотек собраны по типам задач, для решения которых они предназначены. Такая группа называется модулем. Единственный модуль, который использует наша программа - std.stdio , который отвечает за ввод и ввод данных.

Символы и строки: Выражения такие, как "Hello world!" называются строками , и элементы строк называются символами . Единственная строка в нашей программе содержит символы "H" , "e" , "!" и другие.

Порядок выполнения: Программы выполняют свои задачи с помощью вызова операций в определенном порядке. Выполнение задач начинается с операций, которые написаны в функции с названием main . Единственная операция в нашей программе выводит строку "Hello world!" .

Важность регистра: Можно выбирать любые символы внутри строк, но вы должны использовать остальные символы точно так, как они появляются в программе. Это так, так как в программах на D важен регистр. Например, writeln и Writeln являются двумя разными именами.

Мы рассмотрим все эти особенности D подробнее в следующих главах.

Упражнения

  • Написать программу, которая выводит что-нибудь другое.
  • Измените программу, чтобы она выводила более чем одну строку.
  • Попробуйте скомпилировать программу после других изменений: например, уберите точку с запятой в конце строки с writeln и изучите ошибку компиляции.

Решения

  • import std.stdio ;

    void main()
    {
    writeln("Something else... :p" ) ;
    }

  • import std.stdio ;

    void main()
    {
    writeln("A line..." ) ;
    writeln("Another line..." ) ;
    }

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

    import std.stdio ;

    void main()
    {
    writeln("Hello world!" )
    }

writeln and write

В предыдущей главе мы увидели, что writeln берет строку внутри круглых скобок и печатает ее.

Части программы, которые фактически делают работу называются функциями и информация, которая им нужна для выполненения работы, называется параметрами . Акт передачи такой информации функции называется передачей значений параметров в функцию. Параметры передаются в функцию между круглыми скобками и разделяются запятыми.

Заметка: Слово параметр описывает информацию, которая передается в функцию на концептуальном уровне. Конкретная информация, которая на самом деле передается во время выполнения программы называется аргументом. Хотя и неправильно, но эти термины иногда заменяют друг друга в индустрии программного обеспечения.

writeln может брать более одного аргумента. Она печатает их последовательно, один за другим на той же самой строке:

import std.stdio ;

void main()
{
writeln("Hello world!" , "Hello fish!" ) ;
}

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

writeln переходит на следующую строку, write остается на той же:

import std.stdio ;

void main()
{
// Напишем то, что можем на данный момент
write("Hello" ) ;

// ... предположим, что здесь есть еще операции...

Write("world!" ) ;

// ... и наконец:
writeln() ;
}

Вызов writeln без параметров просто завершает текущую строку.

Строки, которые начинаются с // , называются строками комментариев или просто

Возможности, унаследованные от C++

Синтаксис языка D схож с синтаксисом C++ и C#, что облегчает его изучение людям, знакомым с этими языками, а также перенос исходного кода с С++.

Прямой доступ к API языка C

Язык D не только имеет типы данных, соответствующие типам данных языка C, но и обеспечивает прямой доступ к функциям языка C. В таком случае нет необходимости писать функции-обертки (wrapper functions) или копировать значения членов составных типов по одному.

Поддержка всех типов данных языка C

Это делает возможным взаимодействие с API языка C или с кодом существующей библиотеки языка C. Эта поддержка включает структуры, объединения, перечисления, указатели и все типы данных, введённые в стандарте C99.

Обработка исключений операционной системы

Механизм обработки исключений языка подключается к механизмам обработки исключений операционной системы.

Использование существующих инструментариев

Код на языке D преобразуется в объектный файл стандартного формата, что делает возможным использование стандартных ассемблеров, компоновщиков, отладчиков, профайлеров и компрессоров исполняемых файлов.

Управление проектом

Контроль версий

В языке D реализована встроенная поддержка генерирования нескольких версий программ из одного исходного кода. Это заменяет использование команд препроцессора #if и #endif .

Устаревание

Код со временем изменяется, поэтому старый код со временем заменяется новым, улучшенным. Старая версия кода должна быть доступна для обратной совместимости, но может быть отмечена как не рекомендованная к использованию (deprecated). Это облегчает работу команды поддержки по определению зависимостей от устаревшего кода.

Отсутствие предупреждений (warnings)

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

Отсутствие макросов

Отказ создателей языка от препроцессора (как, например, в языке Си) многие расценивают как рискованный и неверный шаг. Но в D имеются встроенные средства, которые позволяют обходиться без препроцессора. По мнению многих, препроцессор C чересчур мощный и опасный.

Стандартная библиотека

В отличие от многих других языков, в D две стандартные библиотеки : Phobos и Tango. Phobos поставляется вместе с компилятором .

Существование двух разных стандартных библиотек создаёт некоторые трудности. Для написания кода, работающего с обеими библиотеками, можно использовать следующую конструкцию:

Version (Tango) { // Код, работающий с функциями Tango } else { // Код, работающий с функциями Phobos }

Tangobos

Для того чтобы решить проблемы с библиотеками, был запущен проект Tangobos. Tangobos - обёртка Tango, дающая программисту интерфейс Phobos.

Примеры

Эта программа печатает аргументы командной строки. Функция main является точкой входа программы, а args - массив с параметрами запуска программы.

Module main; // Желательно объявлять version (Tango) import tango.io .Stdout ; else import std.stdio ; // для writefln() void main(char args) { foreach (int i, char a; args) { version (Tango) Stdout.formatln ("args[{}] = {}" , i, a) ; else writefln("args[%d] = "%s"" , i, a) ; } }

См. также

  • Сравнение возможностей D с другими языками см. в статье Сравнение языков программирования

Примечания

  1. Список изменений в компиляторе языка Ди версии 1.0 . Архивировано
  2. Список изменений в компиляторе языка Ди версии 2.0 . Архивировано из первоисточника 1 июня 2012.
  3. (англ.)
  4. D 2.0 changelog . Архивировано из первоисточника 1 июня 2012. Проверено 11 января 2009. (англ.)
  5. Исторический момент включения исходных текстов в dmd 1.041 (англ.)
  6. D x86 Inline Assembler Встроенный ассемблер в D
  7. Пример программы на D (англ.)
  8. Модульное тестирование D программы (англ.)
  9. Контрактное программирование в D (англ.)
  10. Embedded Documentation Встроенный генератор документации языка D (англ.)
  11. dmd может копмилировать программы из html файла (англ.)
  12. Named Return Value Optimization Личная техника оптимизации Уолтера Брайта (англ.)
  13. The C Preprocessor Versus D - digitalmars.com (англ.)
  14. Conditional Compilation - digitalmars.com (англ.)
  15. std.bitmanip (англ.)
  16. Статические конструкторы в D
  17. [Яык программирования C. Второе издание. Введение: Как и у любых языков, у C есть свои недостатки: некоторые операции имеют нелогичный приоритет <…>]

Ссылки

  • D Programming Language (англ.) . - официальный сайт. Архивировано из первоисточника 19 мая 2012. Проверено 10 мая 2012.
  • Сайт языка D в России (15 мая 2008). Архивировано из первоисточника 19 мая 2012.


Есть вопросы?

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: