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


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


person bhups    schedule 10.07.2010    source источник
comment
Потому что компиляторы C и C ++ - это программы, которые достаточно сложны, чтобы содержать специальную, неформально заданную, изобилующую ошибками, медленную реализацию половины Common Lisp!   -  person Artelius    schedule 10.07.2010
comment
У препроцессора C есть определенные преимущества для выполнения некоторых причудливых вещей, требующих отражения на других языках. Однако с абстракцией платформы и кодом отладки лучше справляться другими способами.   -  person Chris Arguin    schedule 10.07.2010


Ответы (11)


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

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

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

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

person Eli Bendersky    schedule 10.07.2010
comment
да .. Меня больше всего беспокоит вопрос: должен ли я полагаться на то, что компилятор будет достаточно умен, чтобы выполнять всю оптимизацию, например, удаление мертвого кода и т. д.? - person bhups; 10.07.2010
comment
@bhups: определенно. Вы даже полагаетесь на компиляторы C / C ++, чтобы удалить мертвый код и не засорять свой код операторами условной компиляции. - person Eli Bendersky; 10.07.2010
comment
Не забывайте шаблоны как альтернативу макросам в C ++. - person Georg Fritzsche; 18.07.2010
comment
-1 Поскольку, несмотря на то, что ссылка у меня даже не работает, похоже, что URL ведет на какой-то приватный блок / форум. Использование только случайного человека в качестве ссылки для утверждения просто бессмысленно, если есть первоисточник, который НЕ говорит то же самое. - person dhein; 10.07.2017
comment
... способ добиться тех же эффектов. Извините, но ошибаюсь. Другие языки часто не могут делать то же самое. Есть некоторые требования, которые требуют наличия препроцессора, особенно в небольших встроенных системах. Другой способ - написать 128 разных версий одного и того же кода, что тоже плохая идея. - person 12431234123412341234123; 06.12.2019

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

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

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

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

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

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

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

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

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

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

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

person Norman Ramsey    schedule 10.07.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.";
    }
}

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


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

person Billy ONeal    schedule 10.07.2010
comment
И ... как мне добавить объявления данных и типов с помощью оператора if среды выполнения? Ваша схема не подходит для интересного управления конфигурацией. - person Ira Baxter; 10.07.2010
comment
И ... как мне изменить поведение с помощью параметра команды сборки, не редактируя исходный файл? - person Chris Dodd; 10.07.2010
comment
@Ira && @Chris: Я не говорю, что мне нравится это, но я говорю, что именно поэтому многие языки избегают препроцессора. - person Billy ONeal; 10.07.2010
comment
+1 для редактирования / примечания. Я бы хотел более мощный CPP, но я бы предпочел этот, чем полагаться на DCE. - person David X; 18.07.2010
comment
Проблема в том, что когда есть функция, доступная только на одной платформе, компилятор будет жаловаться, когда функции нет, даже если вы никогда не вызываете эту функцию. - person 12431234123412341234123; 06.12.2019

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

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

person Borealid    schedule 10.07.2010
comment
НЕТ! Вы хотите, чтобы условное выражение / макросы взаимодействовали с языком таким образом, чтобы не требовать от вас или ваших инструментов анализа, что значит иметь половину ключевого слова (или любые другие последствия взлома строки ) введенный макросом. Это означает, что ваш язык должен включать эти возможности контролируемым и разумным образом. - person Ira Baxter; 10.07.2010
comment
@ Ира Бакстер: Предварительная обработка текста существует. У него есть пределы. Все мы о них знаем. Но он делает то, что написано на банке. - person Borealid; 10.07.2010
comment
Очевидно, вы не пытались создать инструмент статического анализа для C, который рассуждает о программе, не расширяя директивы препроцессора. Вы НЕ хотите, чтобы строковый процессор с поддержкой Тьюринга стоял между вашим статическим анализом и реальным кодом; если вы считаете, что препроцессор C плох, подождите, пока вы не встретите какой-нибудь yahoo, который кодирует программу факторинга M4, которая условно склеивает ключевые слова в зависимости от ответа. (Условные выражения C достаточно плохи). И препроцессор C делает то, что он говорит на бумаге. Это не помогает. - person Ira Baxter; 10.07.2010
comment
Нет ничего особенного в использовании макроса (текстового) процессора поверх любого источника. Но cpp больше подходит для C (C ++, Objective-C) или других языков, которые разделяют с ними некоторые лексические правила. Я думаю, что общий препроцессор, такой как M4, настроенный соответствующим образом для языка, может быть использован профессионально. - person ShinTakezou; 10.07.2010

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

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

person abelenky    schedule 10.07.2010
comment
Вы можете разумно запустить его только в файлах, которые содержат токены, которые выглядят как токены C (FORTRAN соответствует этому тесту; Python и APL - нет). - person Ira Baxter; 10.07.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 независимо от того, истинна ли отладка.

person joe snyder    schedule 10.07.2010

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

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

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

person Ferruccio    schedule 18.07.2010

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

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

person Jörg W Mittag    schedule 10.07.2010
comment
Что касается мира метапрограммирования на языках, которые предоставляют вам доступ к голому железу в стиле C / C ++, я бы также добавил D в этот список. - person dsimcha; 10.07.2010
comment
CPP также является псевдо-полным по Тьюрингу (псевдо, потому что у вас есть некоторые ограничения памяти, как и у любого реального приложения). - person 12431234123412341234123; 06.12.2019

Из-за уменьшения размера двоичного файла:

  1. Можно сделать другими способами (например, сравните средний размер исполняемого файла C ++ с исполняемым файлом C #).
  2. Это не так важно, если сравнить это с возможностью писать программы, которые действительно работают.
person Assaf Lavie    schedule 10.07.2010
comment
1. намекает на то, что двоичные файлы C # меньше? Если это так, помните, что стандартные среды выполнения обоих языков очень различаются по размеру. - person Georg Fritzsche; 10.07.2010
comment
@Georg, да, но CLR позволяет гораздо больше. В C ++ вы также добавляете тонны больших сторонних двоичных файлов, просто чтобы делать базовые вещи, которые вы получаете с CLR. например размер приложения, использующего некоторые из скомпилированных библиотек Boost, огромен. - person Assaf Lavie; 12.07.2010

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

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

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

person Chris Arguin    schedule 10.07.2010
comment
Вы также можете использовать динамически загружаемые модули в C? - person Georg Fritzsche; 10.07.2010
comment
Да, за счет использования общих библиотек. Строго говоря, это не часть языка C, но часть POSIX. - person Chris Arguin; 10.07.2010

Еще один момент, о котором никто не упомянул, - это поддержка платформы.

Большинство современных языков не могут работать на тех же платформах, что и C или C ++, и не предназначены для работы на этих платформах. Например, Java, Python, а также собственные скомпилированные языки, такие как C #, нуждаются в куче, они предназначены для работы в ОС с управлением памятью, библиотеками и большим объемом пространства, они не работают в автономной среде. Там вы можете использовать другие способы заархивировать то же самое. C можно использовать для программирования контроллеров с ПЗУ 2 КБ, там вам понадобится препроцессор для большинства приложений.

person 12431234123412341234123    schedule 06.12.2019