Интересно, почему другие языки не поддерживают эту функцию. Насколько я понимаю, код C / C ++ зависит от платформы, поэтому для его работы (компиляции и выполнения) на различных платформах используются директивы препроцессора. И помимо этого есть много других применений этого. Как будто вы можете поместить все свои отладочные printf внутрь #if DEBUG ... #endif
. Таким образом, при создании сборки релиза эти строки кода не компилируются в двоичный файл.
Но на других языках выполнение этой задачи (более поздняя часть) затруднено (или может оказаться невозможным, я не уверен). Весь код будет скомпилирован в двоичный файл, увеличивая его размер. Итак, мой вопрос: «Почему Java, or other modern compiled languages
не поддерживает такую функцию?» который позволяет вам очень удобно включать или исключать какой-то фрагмент кода из двоичного файла.
Почему другие языки не поддерживают что-то похожее на директивы препроцессора, такие как C и его потомок?
Ответы (11)
Основные языки, в которых нет препроцессора, обычно имеют другой, часто более чистый способ достижения тех же эффектов.
Наличие препроцессора текста, такого как cpp
, является смешанным благословением. Поскольку cpp
на самом деле не знает C, все, что он делает, это преобразовывает текст в другой текст. Это вызывает множество проблем с обслуживанием. Возьмем, к примеру, C ++, где многие виды использования препроцессора явно устарели в пользу более совершенных функций, таких как:
- Для констант
const
вместо#define
- Для небольших функций
inline
вместо макросов#define
Часто задаваемые вопросы по C ++ называет макросы злом и дает несколько причин избегать их использования.
Преимущества переносимости препроцессора намного перевешиваются возможностью злоупотреблений. Вот несколько примеров из реальных кодов, которые я видел в промышленности:
Тело функции настолько запутывается с
#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 КБ пространства данных; компилятор, достаточно сложный для встраивания через границы модуля, был почти немыслим. Сегодня такие компиляторы стали обычным делом. Некоторые продвинутые компиляторы встраивают и специализируют методы динамически.
Резюме: используя лингвистические механизмы, а не текстовую замену, чтобы изолировать платформенно-зависимый код, вы предоставляете компилятору весь код, по крайней мере, все проверяется на тип, и у вас есть возможность делать такие вещи, как статический анализ, чтобы обеспечить подходящее покрытие тестами. Вы также исключаете целый ряд практик программирования, которые приводят к нечитаемому коду.
Поскольку современные компиляторы достаточно умны, чтобы удалять мертвый код в большинстве случаев, больше нет необходимости вручную загружать компилятор таким образом. Т.е. вместо :
#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 ++ я бы никогда этого не сделал - я бы использовал препроцессор, если ничего другого, что он дает читателю моего кода мгновенно понять, что его часть не должна соблюдаться при определенных условиях. Однако я говорю, что именно поэтому многие языки избегают препроцессора.
Другие языки поддерживают эту функцию, используя общий препроцессор, такой как m4.
Неужели мы действительно хотим, чтобы у каждого языка была своя собственная реализация подстановки текста перед выполнением?
Препроцессор C может быть запущен с любым текстовым файлом, это не обязательно C.
Конечно, если он запущен на другом языке, он может токенизироваться странным образом, но для простых блочных структур, таких как #ifdef DEBUG, вы можете поместить его на любом языке, запустить на нем препроцессор C, а затем запустить компилятор для вашего языка на это, и это будет работать.
Обратите внимание, что макросы / предварительная обработка / условные выражения / и т. Д. Обычно считаются функцией компилятора / интерпретатора, а не функцией языка, потому что они обычно полностью независимы от формального определения языка и могут варьироваться от компилятора к реализации компилятора для одного и того же языка.
Ситуация на многих языках, где директивы условной компиляции могут быть лучше, чем исполняемый код 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 независимо от того, истинна ли отладка.
Лучше задать вопрос: почему C прибегает к использованию препроцессора для реализации такого рода задач метапрограммирования? Это не столько особенность, сколько компромисс с технологиями того времени.
Директивы препроцессора в C были разработаны в то время, когда машинные ресурсы (скорость процессора, оперативная память) были дефицитными (и дорогими). Препроцессор предоставил возможность реализовать эти функции на медленных машинах с ограниченной памятью. Например, первая машина, которой я когда-либо владел, имела 56 КБ ОЗУ и процессор 2 МГц. В нем по-прежнему был доступен полный компилятор K&R C, который доводил ресурсы системы до предела, но был работоспособен.
Более современные языки используют преимущества современных более мощных машин, чтобы предоставить более эффективные способы обработки разновидностей задач метапрограммирования, с которыми раньше справлялся препроцессор.
Многие современные языки на самом деле обладают возможностями синтаксического метапрограммирования, которые выходят далеко за рамки CPP. Практически все современные Лиспы (Arc, Clojure, Common Lisp, Scheme, newLISP, Qi, PLOT, MISC, ...), например, имеют чрезвычайно мощные (фактически, по Тьюрингу) макросистемы, поэтому почему они должны ограничиваться дерьмовыми макросами в стиле CPP, которые даже не являются настоящими макросами, а просто текстовыми фрагментами?
Другие языки с мощным синтаксическим метапрограммированием включают Io, Ioke, Perl 6, OMeta, Converge.
Из-за уменьшения размера двоичного файла:
- Можно сделать другими способами (например, сравните средний размер исполняемого файла C ++ с исполняемым файлом C #).
- Это не так важно, если сравнить это с возможностью писать программы, которые действительно работают.
Другие языки также имеют лучшую динамическую привязку. Например, у нас есть код, который мы не можем отправить некоторым клиентам по причинам экспорта. Наши библиотеки "C" используют операторы #ifdef
и тщательно продуманные трюки с Makefile (что почти то же самое).
В коде Java используются плагины (например, Eclipse), поэтому мы просто не отправляем этот код.
Вы можете сделать то же самое в C, используя разделяемые библиотеки ... но препроцессор намного проще.
Еще один момент, о котором никто не упомянул, - это поддержка платформы.
Большинство современных языков не могут работать на тех же платформах, что и C или C ++, и не предназначены для работы на этих платформах. Например, Java, Python, а также собственные скомпилированные языки, такие как C #, нуждаются в куче, они предназначены для работы в ОС с управлением памятью, библиотеками и большим объемом пространства, они не работают в автономной среде. Там вы можете использовать другие способы заархивировать то же самое. C можно использовать для программирования контроллеров с ПЗУ 2 КБ, там вам понадобится препроцессор для большинства приложений.