Русский
Русский
English
Статистика
Реклама

Betterc

Перевод D как улучшенный C

16.07.2020 20:13:21 | Автор: admin

Уолтер Брайт великодушный пожизненный диктатор языка программирования D и основатель Digital Mars. За его плечами не один десяток лет опыта в разработке компиляторов и интерпретаторов для нескольких языков, в числе которых Zortech C++ первый нативный компилятор C++. Он также создатель игры Empire, послужившей основным источником вдохновения для Sid Meiers Civilization. Данная публикация первая в серии статей о режиме Better C в языке D.


Язык D был с самого начала спроектирован так, чтобы легко и напрямую обращаться к C и, в меньшей степени, C++. Благодаря этому в нём доступны бесчисленные C-библиотеки, стандартная библиотека C и конечно же системные API, которые как правило построены на API языка C.


Но C это не только библиотеки. На C написаны многие большие и неоценимо полезные программы, такие как операционная система Linux и значительная часть программ для неё. И хотя программы на D могут обращаться к библиотекам на C, обратное неверно. Программы на C не могут обращаться к коду на D. Невозможно (или по крайней мере очень сложно) скомпилировать несколько файлов на D и слинковать их в программу на C. Проблема в том, что скомпированные файлы на D могут обращаться к чему-то, что существует только в рантайме D, а добавлять его в линковку обычно оказывается непрактично (рантайм довольно объёмный).


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


Так было до тех пор, пока не появился Better C.


Это всё уже было, идея не новая. Бьёрн Страуструп в 1988 году написал статью под названием A Better C. Его ранний компилятор C++ мог компилировать код на C почти без изменений, и можно было начать использовать возможности C++ тут и там, где это имело смысл не жертвуя существующими наработками на C. Это была блестящая стратегия, обеспечившая ранний успех C++.


Более современный пример Kotlin, в котором использовался другой подход. Синтаксически Kotlin не совместим с Java, однако он обеспечивает двустороннее взаимодействие с существующими Java-библиотеками, что позволяет постепенно мигрировать с Java на Kotlin. Kotlin действительно улучшенная Java, и его успех говорит за себя.


D как улучшенный C


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


Остаётся ли урезанная версия D языком D? На это не так просто ответить, и на самом деле это лишь вопрос личных предпочтений. Основная часть языка остаётся на месте. Однозначно остаются все свойства, аналогичные C. В результате мы получаем язык, промежуточный между C и D.


Что убрано


Очевидно, что убран сборщик мусора, а вместе с ним возможности, которые от него зависят. Память всё ещё можно выделять точно так же, как и в C: при помощи malloc или собственного аллокатора.


Классы C++ и COMвсё ещё будут работать, но полиморфные классы D нет, поскольку они полагаются на сборщик мусора.


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


На сегодняшний день в Better C уже стали доступны RAII и юниттесты. (прим. пер.)

Проверки assert изменены, чтобы использовать библиотеку C вместо рантайма D.


(Это неполный список, см. спецификацию Better C).


Что осталось


Гораздо более важно, что может предложить Better C по сравнению C?


Программистов на C в первую очередь могут заинтересовать безопасность доступа к памяти в виде проверок границ массивов, запрет на утечку указателей из области видимости и гарантированная инициализация локальных переменных. Далее следуют возможности, которые ожидаются от современного языка: модульность, перегрузка функций, конструкторы, методы, Юникод, вложенные функции, замыкания, выполнение функций на стадии компиляции (Compile Time Function Execution, CTFE), генератор документации, продвинутое метапрограммирование и проектирование через интроспекцию (Design by Introspection, DbI).


Генерируемый код


Возьмём следующую программу на С:


#include <stdio.h>int main(int argc, char** argv) {    printf("hello world\n");    return 0;}

Она скомпилируется в:


_main:push EAXmov [ESP],offset FLAT:_DATAcall near ptr _printfxor EAX,EAXpop ECXret

Размер исполняемого файла 23068 байт.


Перенесём её на D:


import core.stdc.stdio;extern (C) int main(int argc, char** argv) {    printf("hello world\n");    return 0;}

Размер исполняемого файла тот же самый: 23068 байт. Это неудивительно, потому что и компилятор C, и компилятор D генерируют один и тот же код, поскольку используют один и тот же генератор кода. (Эквивалентная программа на полноценном D занимала бы 194 Кб). Другими словами, вы ничего не платите за использование D вместо C при аналогичном коде.


Но Hello World это слишком просто. Возьмём что-то посложнее: пресловутый бенчмарк на основе решета Эратосфена:


#include <stdio.h>/* Eratosthenes Sieve prime number calculation. */#define true    1#define false   0#define size    8190#define sizepl  8191char flags[sizepl];int main() {    int i, prime, k, count, iter;    printf ("10 iterations\n");    for (iter = 1; iter <= 10; iter++) {        count = 0;        for (i = 0; i <= size; i++)            flags[i] = true;        for (i = 0; i <= size; i++) {            if (flags[i]) {                prime = i + i + 3;                k = i + prime;                while (k <= size) {                    flags[k] = false;                    k += prime;                }                count += 1;            }        }    }    printf ("\n%d primes", count);    return 0;}

Перепишем на Better C:


import core.stdc.stdio;extern (C):__gshared bool[8191] flags;int main() {    int count;    printf("10 iterations\n");    foreach (iter; 1 .. 11) {        count = 0;        flags[] = true;        foreach (i; 0 .. flags.length) {            if (flags[i]) {                const prime = i + i + 3;                auto k = i + prime;                while (k < flags.length) {                    flags[k] = false;                    k += prime;                }                count += 1;            }        }    }    printf("%d primes\n", count);    return 0;}

Выглядит почти так же, но кое-что следует отметить:


  • Приписка extern(C) означает использование соглашения о вызове из языка C.
  • D обычно хранит статические данные в локальном хранилище потока (thread-local storage, TLS). C же хранит их в глобальном хранилище. Аналогичного поведения мы достигаем при помощи __gshared.
  • Инструкция foreach более простой способ пройтись циклом по известному промежутку.
  • Использование const даёт знать читателю, что prime никогда не изменится после инициализации.
  • Типы iter, i, prime и k выводятся автоматически, уберегая от ошибок с непредвиденным приведением типов.
  • За количество элементов в flags отвечает flags.length, а не какая-то независимая переменная.

Последний пункт ведёт к скрытому, но очень важному преимуществу: при обращении к массиву flags происходит проверка его границ. Больше никаких ошибок из-за выхода за границы массива! И нам для этого даже не нужно было ничего делать.


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


От себя лично могу сказать, что с тех пор, как появилась опция -betterC, я начал переводить на D многие мои старые, но всё ещё используемые программы по одной функции за раз. Работая по одной функции и запуская набор тестов после каждого изменения я постоянно сохраняю программу в рабочем состоянии. Если что-то сломалось, мне нужно проверить только одну функцию, чтобы найти причину. Мне не очень интересно дальше поддерживать свои программы на C, и с появлением Better C для этого больше нет причин.

Подробнее..
Категории: C++ , C , D , Dlang , Betterc

Перевод Баги, которые разрушили ваш замок

19.07.2020 00:15:31 | Автор: admin

Уолтер Брайт великодушный пожизненный диктатор языка программирования D и основатель Digital Mars. За его плечами не один десяток лет опыта в разработке компиляторов и интерпретаторов для нескольких языков, в числе которых Zortech C++ первый нативный компилятор C++. Он также создатель игры Empire, послужившей основным источником вдохновения для Sid Meiers Civilization. Данная публикация первая в серии статей о режиме Better C в языке D.


Цикл статей о Better C
  1. D как улучшенный C
  2. Баги, которые разрушили ваш замок
  3. Портируем make.c на D

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


А может, дело не в вас? Я покажу вам, что эти ошибки не ваша вина: это вина инструментов, и если только улучшить инструменты, то ваш замок будет в безопасности.


И вам даже не придётся идти ни на какие компромисы.


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


Возьмём обычную программу для подсчёта суммы значений массива:


#include <stdio.h>#define MAX 10int sumArray(int* p) {    int sum = 0;    int i;    for (i = 0; i <= MAX; ++i)        sum += p[i];    return sum;}int main() {    static int values[MAX] = { 7,10,58,62,93,100,8,17,77,17 };    printf("sum = %d\n", sumArray(values));    return 0;}

Программа должна напечатать:


sum = 449

И именно это она и делает на моей машине с Ubuntu Linux: и в gcc, clang, и даже с флагом -Wall. Уверен, вы уже догадались, в чём ошибка:


for (i = 0; i <= MAX; ++i)              ^^

Это классическая ошибка на единицу. Цикл выполняется 11 раз вместо 10. Должно быть так:


for (i = 0; i < MAX; ++i)

Обратите внимание, что несмотря на баг, программа всё равно вывела правильный результат! Во всяком случае, на моей машине. И я бы не обнаружил этой ошибки. А вот на машине пользователя она бы загадочно всплыла, и я бы столкнулся с багом Гейзенберга на удалённой машине. Я уже съёживаюсь в предвкушении того, сколько времени и денег мне это будет стоить.


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


  1. никогда-никогда не использовать промежутки, включающие верхнюю границу;
  2. никогда-никогда не использовать <= в проверке цикла.

Став лучшим программистом, я решил проблему! Или нет? На самом деле нет. Давайте посмотрим на этот код с точки зрения бедняги, которому придётся его проверять. Он хочет убедиться, что sumArray работает корректно. Для этого он должен:


  1. Найти все функции, вызывающие sumArray, и проверить, что за указатель они передают.
  2. Убедиться, что указатель действительно указывает на массив.
  3. Убедиться, что размер массива действительно MAX.

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


Даже если вы не ошибётесь насколько вы можете быть уверены, что всё будет нормально? Если кто-то другой внесёт изменения, то ошибок всё ещё не будет? Хотите заново делать весь этот анализ? Уверен, вам и так есть чем заняться. Это дело инструментов.


Фундаментальная проблема состоит в том, что массивы в С преобразуются в указатели, когда используются как аргумент функции, даже если параметр функции определён как массив. Этой проблемы никак не избежать. И её никак не обнаружить. (По крайней мере, ни gcc, ни clang не умеют обнаруживать эту проблему, но может, кто-то разработал анализатор, который умеет это делать).


Инструмент, который решает эту проблему это компилятор D с опцией -betterC. В D есть такое понятие, как динамический массив, который на самом деле просто толстый указатель, который определён примерно вот так:


struct DynamicArray {    T* ptr;    size_t length;}

Он объявляется вот так:


int[] a;

И наш пример становится таким:


import core.stdc.stdio;extern (C):   // use C ABI for declarationsenum MAX = 10;int sumArray(int[] a) {    int sum = 0;    for (int i = 0; i <= MAX; ++i)        sum += a[i];    return sum;}int main() {    __gshared int[MAX] values = [ 7,10,58,62,93,100,8,17,77,17 ];    printf("sum = %d\n", sumArray(values));    return 0;}

Компилируем:


dmd -betterC sum.d

Запускаем:


./sumAssertion failure: 'array overflow' on line 11 in file 'sum.d'

Так-то лучше. Заменяем <= на < и получаем:


./sumsum = 449

Что здесь произошло? В динамическом массиве a хранится его длина, и компилятор вставляет проверки выхода за границы массива.


Но подождите, это ещё не всё.


Ещё там есть вот это вот досадное MAX. Поскольку массив a знает свою длину, то вместо этого можно написать так:


for (int i = 0; i < a.length; ++i)

Это настолько частая идиома, что в D для неё имеется специальный синтаксис:


foreach (value; a)    sum += value;

Теперь вся функция sumArray выглядит вот так:


int sumArray(int[] a) {    int sum = 0;    foreach (value; a)        sum += value;    return sum;}

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


Протестую! скажете вы. Передача массива a в sumArray требует двух проталкиваний в стек, тогда как указатель p требовал только одного. Ты обещал, что не придётся идти на компромиссы, но здесь я жертвую скоростью.


Это правда в случае, если MAX является константой, а не передаётся в функцию, как здесь:


int sumArray(int *p, size_t length);

Но я же обещал, что не придётся идти ни на какие компромиссы. D позволяет предавать параметры по ссылке, в том числе и массивы фиксированной длины.


int sumArray(ref int[MAX] a) {    int sum = 0;    foreach (value; a)        sum += value;    return sum;}

Что здесь происходит? Массив a, будучи параметром с аттрибутом ref, во время выполнения становится всего лишь указателем. Однако он типизирован как указатель на массив из MAX элементов, что позволяет при обращении к нему делать проверки границ. Вам не придётся проверять функции, вызывающие sumArray, потому что система типов гарантируют, что они могут передавать только массивы правильного размера.


Протестую! скажете вы. D поддерживает указатели. Разве я не могу написать так же, как было? Что меня остановит? Я думал, что речь идёт о механических гарантиях!


Да, вы можете написать вот так:


import core.stdc.stdio;extern (C):   // use C ABI for declarationsenum MAX = 10;int sumArray(int* p) {    int sum = 0;    for (int i = 0; i <= MAX; ++i)        sum += p[i];    return sum;}int main() {    __gshared int[MAX] values = [ 7,10,58,62,93,100,8,17,77,17 ];    printf("sum = %d\n", sumArray(&values[0]));    return 0;}

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


sum = 39479

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


Как можно гарантировать, что этого не произойдёт? Добавить в код аттрибут @safe:


import core.stdc.stdio;extern (C):   // use C ABI for declarationsenum MAX = 10;@safe int sumArray(int* p) {    int sum = 0;    for (int i = 0; i <= MAX; ++i)        sum += p[i];    return sum;}int main() {    __gshared int[MAX] values = [ 7,10,58,62,93,100,8,17,77,17 ];    printf("sum = %d\n", sumArray(&values[0]));    return 0;}

Попытка скомпилировать выдаст следующее:


sum.d(10): Error: safe function 'sum.sumArray' cannot index pointer 'p'

Конечно, во время код-ревью надо будет будет сделать grep, чтобы удостовериться, что используется @safe, но на этом всё.


В общем и целом, этот баг можно победить, не дав массиву преобразоваться в указатель при передаче в функцию в качестве аргумента, и уничтожить насовсем, запретив небезопасные операции над указателями. Я уверен, что мало кто из вас никогда не сталкивался с ошибками переполнения буфера. Ждите следующей части этого цикла. Может быть в следующий раз мы разберёмся с багом, который преодолел ваш ров! (Если в вашем инструментарии вообще есть ров).

Подробнее..
Категории: C++ , C , Отладка , D , Dlang , Betterc

Перевод Портируем make.c на D

20.07.2020 22:15:29 | Автор: admin

Уолтер Брайт великодушный пожизненный диктатор языка программирования D и основатель Digital Mars. За его плечами не один десяток лет опыта в разработке компиляторов и интерпретаторов для нескольких языков, в числе которых Zortech C++ первый нативный компилятор C++. Он также создатель игры Empire, послужившей основным источником вдохновения для Sid Meiers Civilization.


Цикл статей о Better C

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


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


Мне пришла на ум старая make-программа, которую я написал для компилятора C Datalight в начале 1980-х. Это реальная имплементация классической программы make, которая постоянно использовалась с начала 80-х. Она написана на C ещё до его стандартизации, была портирована из одной системы в другую и укладывается всего в 1961 строчку кода, включая комментарии. Она до сих пор регулярно используется.


Вот документация и исходный код. Размер исполняемого файла make.exe 49692 байта, и последнее изменение было 19августа2012 г.


Наш Злобный план:


  1. Свести к минимуму diffы между C- и D-версиями. Таким образом, если поведение программ будет различаться, проще будет найти источник различия.
  2. Во время переноса не будет предпринято попыток исправить или улучшить код на C. Это делается во исполнение пункта 1.
  3. Не будет предпринято попыток рефакторинга кода. Опять же, см. пункт 1.
  4. Воспроизвести поведение программы на C насколько это возможно, со всеми багами.
  5. Сделать всё, что необходимо ради исполнения пункта 4.

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


Спойлеры!


Законченный перенос с C на D. Размер исполняемого файла 52252 байт (сравнимо с оригиналом 49692 байт). Я не анализировал увеличение в размере, но вероятно, оно взялось из-за экземпляров шаблона 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).


/* Delete these old C macro definitions...#ifdef DEBUG-#define debug1(a)       printf(a)-#define debug2(a,b)     printf(a,b)-#define debug3(a,b,c)   printf(a,b,c)-#else-#define debug1(a)-#define debug2(a,b)-#define debug3(a,b,c)-#endif*/// And replace their usage with the debug statement// debug2("Returning x%lx\n",datetime);debug printf("Returning x%lx\n",datetime);

Макросы TRUE, FALSE и NULL при помощи поиска и замены заменены на true, false и null.


Макрос ESC заменён константой времени компиляции. (См. документацию по константам).


// #define ESC     '!'enum ESC =      '!';

Макрос NEWOBJ заменён шаблонной функцией.


// #define NEWOBJ(type)    ((type *) mem_calloc(sizeof(type)))type* NEWOBJ(type)() { return cast(type*) mem_calloc(type.sizeof); }

Макрос filenamecmp заменён функцией.


Убрана поддержка устаревших платформ.


Глобальные переменные в D по умолчанию помещаются в локальное хранилище потока (thread-local storage, TLS). Но поскольку make однопоточная программа, их можно поместить в глобальное хранилище при помощи класса хранилища __gshared. (См. документацию по атрибуту __gshared).


// int CMDLINELEN;__gshared int CMDLINELEN

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


/*typedef struct FILENODE        {       char            *name,genext[EXTMAX+1];                char            dblcln;                char            expanding;                time_t          time;                filelist        *dep;                struct RULE     *frule;                struct FILENODE *next;        } filenode;*/struct FILENODE{        char            *name;        char[EXTMAX1]  genext;        char            dblcln;        char            expanding;        time_t          time;        filelist        *dep;        RULE            *frule;        FILENODE        *next;}alias filenode = FILENODE;

В языке D macro это ключевое слово, поэтому вместо этого будем использовать MACRO.


В отличие от C, в языке D звёздочка в объявлении указателя является частью типа, поэтому при объявлении нескольких указателей звёздочка применяется к каждому символу:


// char *name,*text;// In D, the * is part of the type and // applies to each symbol in the declaration.char* name, text;

Объявления массивов в стиле C преобразованы в объявления в стиле D. (См. документацию по синтаксису объявлений в D).


Слово static на уровне модуля в D ничего не значит. В C статические глобальные переменные эквивалентны приватным переменным уровня модуля в D, но это неважно, если модуль никогда не импортируется. Их всё ещё нужно обозначить как __gshared, и этот можно сделать целым блоком. (См. документацию по атрибуту static).


/*static ignore_errors = FALSE;static execute = TRUE;static gag = FALSE;static touchem = FALSE;static debug = FALSE;static list_lines = FALSE;static usebuiltin = TRUE;static print = FALSE;...*/__gshared{    bool ignore_errors = false;    bool execute = true;    bool gag = false;    bool touchem = false;    bool xdebug = false;    bool list_lines = false;    bool usebuiltin = true;    bool print = false;    ...}

Предварительные объявления функций в языке D не нужны. Функцию, определённую на уровне модуля, можно вызывать из любого места в этом модуле, даже до её определения.


В расширении символов подстановки в make-программе нет большого смысла.


Параметры функций, определённые с синтаксисом массивов, на самом деле являются указателями, и в D объявляются как указатели.


// int cdecl main(int argc,char *argv[])int main(int argc,char** argv)

Макрос mem_init() ни во что не расширяется, и до этого мы его убрали.


В C можно грязно играть с аргументами, но D требует, чтобы они соответствовали прототипу функции.


void cmderr(const char* format, const char* arg) {...}// cmderr("can't expand response file\n");cmderr("can't expand response file\n", null);

При помощи глобального поиска и замены оператор-стрелка (->) языка C заменён на точку (.), поскольку в D доступ к членам осуществляется одинаково.


Директивы условной компиляции заменены на version.


/* #if TERMCODE    ... #endif*/    version (TERMCODE)    {        ...    }

Отсутствие прототипов функций свидетельствует о древности этого кода. D требует полноценных прототипов.


// doswitch(p)// char *p;void doswitch(char* p)

В языке D слово debug зарезервировано. Переименуем в xdebug.


Многострочные литералы в C требуют \n\ в конце каждой строки. В D этого не требуется.


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


Выражение static if может во многих случаях заменить #if. (См. документацию по static if).


Массивы в D не сводятся к указателю автоматически, следует использовать .ptr.


// utime(name,timep);utime(name,timep.ptr);

Использование const для строк в стиле C проистекает из строковых литералов в D, поскольку D не позволяет брать изменяемые указатели на строковые литералы. (См. документацию по const и immutable).


// linelist **readmakefile(char *makefile,linelist **rl)linelist **readmakefile(const char *makefile,linelist **rl)

Преобразование void* в char* в D должно быть явным.


// buf = mem_realloc(buf,bufmax);buf = cast(char*)mem_realloc(buf,bufmax);

Тип unsigned заменён на uint.


Атрибут inout можно использовать, чтобы передать константность аргумента функции на возвращаемый тип. Если параметр обозначен как const, то таким же будет возвращаемое значение, и наоборот. (См. документацию по inout-функциям).


// char *skipspace(p) {...}inout(char) *skipspace(inout(char)* p) {...}

Макрос arraysize можно заменить на свойство .length. (См. документацию по свойствам массивов).


// useCOMMAND  |= inarray(p,builtin,arraysize(builtin));useCOMMAND  |= inarray(p,builtin.ptr,builtin.length)

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


// static char envname[] = "@_CMDLINE";char[10] envname = "@_CMDLINE";

Свойство .sizeof служит заменой оператору sizeof() из C. (См. документацию по .sizeof).


// q = (char *) mem_calloc(sizeof(envname) + len);q = cast(char *) mem_calloc(envname.sizeof + len)

Старые версии Windows нас не интересуют.


Доисторическое применение char * заменено на void*.


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


У нас остаётся только файл man.c, который был нужен, чтобы открывать в браузере документацию по make при запуске с опцией -man. К счастью, он уже портирован на D, так что я могу просто скопировать код.


Собрать make так просто, что для этого даже не требуется make-файл:


\dmd2.079\windows\bin\dmd make.d dman.d -O -release -betterC -I. -I\dmd2.079\src\druntime\import\ shell32.lib

Резюме


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


Проблемы, с которыми мы столкнулись, типичны и легко решаются следующими способами:


  • замена #include на import;
  • замена отсутствующих D-версий включаемых файлов;
  • глобальный поиск и замена вещей вроде ->;
  • замена макросов препроцессора на:
    • константы времени компиляции,
    • простые шаблоны,
    • функции,
    • спецификаторы версий,
    • спецификаторы отладки;
  • замена зарезервированных слов;
  • изменение объявлений массивов и указателей;
  • удаление ненужных прототипов функций;
  • более строгое соблюдение типов;
  • использование свойств массивов;
  • замена типов C типами D.

Не потребовалось ничего из следующего:


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

Будущее


Теперь, когда у нас есть Better C, нам доступны многие современные возможности, которые позволят нам улучшить наш код:



К действию


Если вы знаете английский, заходите на форум D и расскажите нам, как продвигается ваш проект на Better C!

Подробнее..
Категории: C , D , Dlang , Betterc

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru