Почему другие языки не поддерживают нечто похожее на директивы препроцессора, такие как C и его потомок? - PullRequest
13 голосов
/ 10 июля 2010


Интересно, почему другие языки не поддерживают эту функцию. Что я могу понять, так это то, что код C / C ++ зависит от платформы, поэтому чтобы заставить его работать (компилировать и выполнять) на разных платформах, достигается использование директив препроцессора. И есть много других применений этого помимо этого. Как вы можете поместить все ваши отладочные printf внутри #if DEBUG ... #endif. Поэтому при сборке релиза эти строки кода не компилируются в двоичном виде.
Но в других языках добиться этого (более поздняя часть) сложно (или может быть невозможно, я не уверен). Весь код будет скомпилирован в двоичном коде, увеличивая его размер. Итак, мой вопрос "почему Java, or other modern compiled languages не поддерживает такую ​​функцию?" что позволяет вам включать или исключать какой-либо фрагмент кода из двоичного кода очень удобным способом.

Ответы [ 10 ]

9 голосов
/ 10 июля 2010

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

Наличие текстового препроцессора, такого как cpp, - смешанное благословение. Поскольку cpp на самом деле не знает C , все, что он делает, это преобразовывает текст в другой текст. Это вызывает много проблем с обслуживанием. Возьмем, например, C ++, где многие виды использования препроцессора явно устарели в пользу улучшенных функций, таких как:

  • Для констант const вместо #define
  • Для небольших функций inline вместо #define макросов

FAQ по C ++ называет макросы злыми и дает несколько причин избегать их использования.

7 голосов
/ 10 июля 2010

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

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

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

  • Когда приложение предназначено для нескольких платформ, становится очень трудно скомпилировать весь код, в отличие от того, какой код выбран для платформы разработчика. Возможно, вам потребуется настроить несколько компьютеров. (Например, в системе BSD дорого настроить среду кросс-компиляции, которая точно имитирует заголовки GNU.) В те дни, когда большинство разновидностей Unix были проприетарными, и поставщики должны были поддерживать их всех, эта проблема была очень серьезной. Сегодня, когда так много версий Unix бесплатны, это не проблема, хотя все еще довольно сложно дублировать собственные заголовки Windows в среде Unix.

  • It Некоторый код защищен таким количеством #ifdef s, что вы не можете понять, какая комбинация опций -D необходима для выбора кода. Проблема в NP- трудно, поэтому самые известные решения требуют экспоненциально много разных комбинаций определений. Это, конечно, непрактично, поэтому реальное следствие состоит в том, что постепенно ваша система заполняется кодом, который не был скомпилирован . Эта проблема убивает рефакторинг, и, конечно, такой код полностью невосприимчив к вашим модульным тестам и вашим регрессионным тестам - если вы не настроили огромную многоплатформенную ферму тестирования, а может быть, даже тогда.

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

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

  • Для некоторых языков, таких как Java, all зависящий от платформы код находится в реализации JVM и в связанных библиотеках. Люди приложили немало усилий, чтобы сделать JVM и библиотеки независимыми от платформы.

  • Во многих языках, таких как Haskell, Lua, Python, Ruby и многих других, разработчики столкнулись с некоторыми трудностями, чтобы уменьшить количество зависимого от платформы кода по сравнению с C.

  • На современном языке вы можете поместить зависимый от платформы код в отдельный модуль компиляции за скомпилированным интерфейсом. Многие современные компиляторы имеют хорошие средства для встраивания функций через границы интерфейса, так что вы не платите много (или вообще) штрафа за этот вид абстракции. Это не относится к Си, потому что (а) нет отдельно скомпилированных интерфейсов; модель отдельной компиляции предполагает #include и препроцессор; и (b) компиляторы C достигли совершеннолетия на машинах с 64 КБ пространства кода и 64 КБ пространства данных; компилятор, достаточно сложный, чтобы встроить его в границы модуля, был почти немыслим. Сегодня такие компиляторы рутинны. Некоторые продвинутые компиляторы встроенные и специализированные методы динамически .

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

6 голосов
/ 10 июля 2010

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

#include <iostream>

#define DEBUG

int main()
{
#ifdef DEBUG
        std::cout << "Debugging...";
#else
        std::cout << "Not debugging.";
#endif
}

вы можете сделать:

#include <iostream>

const bool debugging = true;

int main()
{
    if (debugging)
    {
        std::cout << "Debugging...";
    }
    else
    {
        std::cout << "Not debugging.";
    }
}

, и вы, вероятно, получите такой же или, по крайней мере, похожий вывод кода.


Edit / Note: В C и C ++ я бы абсолютно никогда не делал этого - я бы использовал препроцессор, если бы не что иное, чтобы он сразу дал понять читателю моего кода, что его часть не являетсядолжен соблюдаться при определенных условиях.Я говорю, однако, что именно поэтому многие языки избегают препроцессора.

2 голосов
/ 10 июля 2010

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

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

2 голосов
/ 10 июля 2010

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

Ситуация во многих языках, где директивы условной компиляции могут быть лучше, чем код времени выполнения if-then-else, возникает, когда операторы времени компиляции (такие как объявления переменных) должны быть условными. Например

$if debug
array x
$endif
...
$if debug
dump x
$endif

только объявляет / распределяет / компилирует x при необходимости x, тогда как

array x
boolean debug
...
if debug then dump x

вероятно должен объявить x независимо от того, является ли отладка истинной.

2 голосов
/ 10 июля 2010

Другие языки поддерживают эту функцию, используя универсальный препроцессор, такой как m4.

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

1 голос
/ 18 июля 2010

Лучше задать вопрос: почему C прибегает к использованию препроцессора для реализации такого рода задач метапрограммирования?Это не особенность, а компромисс с технологией того времени.

Директивы препроцессора в C были разработаны в то время, когда ресурсы машины (скорость процессора, оперативная память) были недостаточны (и дорого).Препроцессор предоставил способ реализовать эти функции на медленных машинах с ограниченной памятью.Например, первая машина, которой я когда-либо владел, имела 56 КБ ОЗУ и процессор 2 МГц.У него все еще был полный доступный компилятор K & R C, который выдвигал ресурсы системы до предела, но был работоспособным.

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

1 голос
/ 10 июля 2010

Многие современные языки на самом деле имеют синтаксические возможности метапрограммирования, которые выходят далеко за рамки CPP. Практически все современные Лиспы (Arc, Clojure, Common Lisp, Scheme, newLISP, Qi, PLOT, MISC, ...), например, имеют чрезвычайно мощные (на самом деле, полные по Тьюрингу) макрос, поэтому должны ли они ограничиться дурацкими макросами в стиле CPP, которые даже не являются настоящими макросами, а просто текстовыми фрагментами?

Другие языки с мощным синтаксическим метапрограммированием включают Io, Ioke, Perl 6, OMeta, Converge.

0 голосов
/ 10 июля 2010

Поскольку уменьшение размера двоичного файла:

  1. Можно сделать другими способами (например, сравните средний размер исполняемого файла C ++ с исполняемым файлом C #).
  2. Это не так важно, когда вы сравниваете это с возможностью писать программы, которые действительно работают.
0 голосов
/ 10 июля 2010

Другие языки также имеют лучшее динамическое связывание. Например, у нас есть некоторый код, который мы не можем отправить некоторым клиентам по причинам экспорта. Наши библиотеки "C" используют операторы #ifdef и разрабатывают трюки с Makefile (что почти одинаково).

В коде Java используются плагины (в частности Eclipse), поэтому мы просто не отправляем этот код.

Вы можете сделать то же самое в C с помощью разделяемых библиотек ... но препроцессор намного проще.

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