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

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

Уолтер Брайт великодушный пожизненный диктатор языка программирования 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 для этого больше нет причин.

Источник: habr.com
К списку статей
Опубликовано: 16.07.2020 20:13:21
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

C

C++

D

Dlang

Betterc

Категории

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

© 2006-2020, personeltest.ru