Уолтер Брайт — «великодушный пожизненный диктатор» языка программирования D и основатель Digital Mars. За его плечами не один десяток лет опыта в разработке компиляторов и интерпретаторов для нескольких языков, в числе которых Zortech C++ — первый нативный компилятор C++. Он также создатель игры Empire, послужившей основным источником вдохновения для Sid Meier’s Civilization. Better C — это способ перенести существующие проекты на языке C на язык D в последовательной манере. Эта статья показывает пошаговый процесс конвертации нетривиального проекта из C в D и частые проблемы, которые при этом возникают.  Хотя фронтенд компилятора D dmd уже сконвертирован в D, это настолько большой проект, что его трудно целиком охватить. Мне был нужен более мелкий и скромный проект, который можно было бы целиком держать в голове, но чтобы он не был умозрительным примером. Мне пришла на ум старая make-программа, которую я написал для компилятора C Datalight в начале 1980-х. Это реальная имплементация классической программы make, которая постоянно использовалась с начала 80-х. Она была написана на C ещё до его стандартизации, портировалась из одной системы в другую и укладывается всего в 1961 строчку кода, включая комментарии. Она до сих пор регулярно используется.  Вот документация и исходный код. Размер исполняемого файла make.exe — 49 692 байта, и последнее изменение было 19 августа 2012 г. Наш Злобный план: Только когда мы закончим, можно будет приступить к исправлениям, рефакторингу, подчистке и т.д. Законченный перенос с C на D. Размер исполняемого файла — 52 252 байт (сравнимо с оригиналом — 49 692 байт). Я не анализировал увеличение в размере, но вероятно, оно взялось из-за экземпляров шаблона NEWOBJ (в C-версии это макрос) и изменений в рантайме DMC после 2012 года.  Вот отличия между версиями. Изменились 664 строки из 1961, примерно треть — кажется, что много, но надеюсь, я смогу вас убедить, что почти все отличия тривиальны.  Включение файлов через #include заменено на импортирование соответствующих модулей D: например, #include <stdio.h> заменено на import core.stdc.stdio;. К сожалению, некоторые из включаемых файлов специфичны для Digital Mars C, и для них не существует версий на D (это надо исправить). Чтобы не останавливаться на этом, я просто вставил соответствующие объявления с 29-й строки по 64-ю. (См. документацию по объявлению import). Конструкция #if _WIN32 заменена на version (Windows). (См. документацию по условной компиляции версий и список предопределённых версий). Объявление extern(C): помечает последующие объявления в файле как совместимые с C. (См. документацию по аттрибуту линковки). При помощи глобального поиска и замены макросы debug1, debug2 и debug3 заменены на debug prinf. В целом, директивы препроцессора #ifdef DEBUG заменены на условную компиляцию при помощи debug. (См. документацию по выражению debug). Макросы TRUE, FALSE и NULL при помощи поиска и замены заменены на true, false и null.  Макрос ESC заменён константой времени компиляции. (См. документацию по константам). Макрос NEWOBJ заменён шаблонной функцией. Макрос filenamecmp заменён функцией. Убрана поддержка устаревших платформ. Глобальные переменные в D по умолчанию помещаются в локальное хранилище потока (thread-local storage, TLS). Но поскольку make — однопоточная программа, их можно поместить в глобальное хранилище при помощи класса хранилища __gshared. (См. документацию по атрибуту __gshared). В D нет отдельного пространства имён для структур, так что в typedef нет необходимости. Вместо этого можно использовать alias. (См. документацию по объявлению alias). Кроме того, из объявлений переменных убрано слово struct. В языке D macro — это ключевое слово, поэтому вместо этого будем использовать MACRO. В отличие от C, в языке D звёздочка в объявлении указателя является частью типа, поэтому при объявлении нескольких указателей звёздочка применяется к каждому символу: Объявления массивов в стиле C преобразованы в объявления в стиле D. (См. документацию по синтаксису объявлений в D). Слово static на уровне модуля в D ничего не значит. В C статические глобальные переменные эквивалентны приватным переменным уровня модуля в D, но это неважно, если модуль никогда не импортируется. Их всё ещё нужно обозначить как __gshared, и этот можно сделать целым блоком. (См. документацию по атрибуту static). Предварительные объявления функций в языке D не нужны. Функцию, определённую на уровне модуля, можно вызывать из любого места в этом модуле, даже до её определения.  В расширении символов подстановки в make-программе нет большого смысла. Параметры функций, определённые с синтаксисом массивов, на самом деле являются указателями, и в D объявляются как указатели. Макрос mem_init() ни во что не расширяется, и до этого мы его убрали. В C можно грязно играть с аргументами, но D требует, чтобы они соответствовали прототипу функции. При помощи глобального поиска и замены оператор-стрелка (->) языка C заменён на точку (.), поскольку в D доступ к членам осуществляется одинаково. Директивы условной компиляции заменены на version. Отсутствие прототипов функций свидетельствует о древности этого кода. D требует полноценных прототипов. В языке D слово debug зарезервировано. Переименуем в xdebug. Многострочные литералы в C требуют \n\ в конце каждой строки. В D этого не требуется. Неиспользуемый код закомментирован при помощи вложенного блока комментариев /+ +/. (См. документацию по строчным, блочным и вложенным комментариям). Выражение static if может во многих случаях заменить #if. (См. документацию по static if). Массивы в D не сводятся к указателю автоматически, следует использовать .ptr. Использование const для строк в стиле C проистекает из строковых литералов в D, поскольку D не позволяет брать изменяемые указатели на строковые литералы. (См. документацию по const и immutable). Преобразование void* в char* в D должно быть явным. Тип unsigned заменён на uint. Атрибут inout можно использовать, чтобы передать «константность» аргумента функции на возвращаемый тип. Если параметр обозначен как const, то таким же будет возвращаемое значение, и наоборот. (См. документацию по inout-функциям). Макрос arraysize можно заменить на свойство .length. (См. документацию по свойствам массивов). Строковые литералы неизменяемы (immutable), поэтому изменяемые строки необходимо заменить на массивы, выделенные на стеке. (См. документацию по строковым литералам). Свойство .sizeof служит заменой оператору sizeof() из C. (См. документацию по .sizeof). Старые версии Windows нас не интересуют. Доисторическое применение char * заменено на void*. И вот и все изменения! Как видите, не так уж плохо. Я не выставлял таймер, но сомневаюсь, что всё это заняло у меня больше часа — включая исправление нескольких ошибок, которые я сделал в процессе. У нас остаётся только файл man.c, который был нужен, чтобы открывать в браузере документацию по make при запуске с опцией -man. К счастью, он уже портирован на D, так что я могу просто скопировать код. Собрать make так просто, что для этого даже не требуется make-файл: Мы придерживались нашего Злобного плана по портированию нетривильной программы на олдскульном C на язык D, и смогли сделать это быстро и корректно. Мы получили эквивалентный исполняемый файл. Проблемы, с которыми мы столкнулись, типичны и легко решаются следующими способами: Не потребовалось ничего из следующего: Теперь, когда у нас есть Better C, нам доступны многие современные возможности, которые позволят нам улучшить наш код: Если вы знаете английский, заходите на форум D и расскажите нам, как продвигается ваш проект на Better C!