Почему другие языки не поддерживают нечто подобное директивам препроцессора, таким как C и его потомкам?
Интересно, почему другие языки не поддерживают эту функцию. Я понимаю, что C/С++-код зависит от платформы, поэтому для его работы (компиляции и выполнения) на разных платформах достигается использование препроцессорных директив. И кроме этого есть много других применений. Как вы можете поместить весь свой отладочный printf внутри #if DEBUG ... #endif
. Поэтому, создавая выпуск, эти строки кода не скомпилируются в двоичном формате.
Но на других языках достижение этой вещи (более поздняя часть) затруднено (или может быть невозможно, я не уверен). Весь код будет скомпилирован в двоичном формате, увеличивая его размер. Поэтому мой вопрос: "Почему Java, or other modern compiled languages
не поддерживает эту функцию?" который позволяет вам включать или исключать некоторые фрагменты кода из двоичного файла очень удобным способом.
Ответы
Ответ 1
Основные языки, у которых нет препроцессора, обычно имеют другой, зачастую более чистый, способ достижения тех же самых эффектов.
Наличие текстового препроцессора, такого как cpp
, является смешанным благословением. Поскольку cpp
фактически не знает C, все, что он делает, это преобразовать текст в другой текст. Это вызывает множество проблем с обслуживанием. Возьмите С++, например, когда многие применения препроцессора явно запрещены в пользу лучших функций, таких как:
- Для констант
const
вместо #define
- Для небольших функций
inline
вместо #define
макросов
С++ FAQ вызывает макрос зла и дает несколько причин, чтобы избежать их использования.
Ответ 2
Преимущества превентора в переносимости намного перевешиваются возможностями для злоупотреблений. Вот несколько примеров из реальных кодов, которые я видел в промышленности:
-
Тело функции становится настолько запутанным с #ifdef
, что очень сложно прочитать функцию и выяснить, что происходит. Помните, что препроцессор работает с текстом, а не с синтаксисом, поэтому вы можете делать вещи, которые дико неграмматичны
-
Код может дублироваться в разных ветвях #ifdef
, что затрудняет поддержание единственной точки правды о том, что происходит.
-
Когда приложение предназначено для нескольких платформ, очень сложно скомпилировать весь код, в отличие от того, какой код будет выбран для платформы разработчика. Возможно, вам понадобится настроить несколько компьютеров. (Это дорого, скажем, в BSD-системе, чтобы создать среду кросс-компиляции, которая точно имитирует заголовки GNU.) В те дни, когда большинство разновидностей Unix были патентованы, а поставщикам приходилось их поддерживать, эта проблема была очень серьезной. Сегодня, когда так много версий Unix бесплатны, это меньше проблем, хотя по-прежнему довольно сложно дублировать собственные заголовки Windows в среде Unix.
-
Этот код защищен множеством #ifdef
, который не может определить, какая комбинация параметров -D
необходима для выбора кода. Проблема NP- жесткие, поэтому для наиболее известных решений требуется многократно использовать множество комбинаций определений. Это, конечно, непрактично, поэтому реальное последствие заключается в том, что постепенно ваша система заполняется кодом, который не был скомпилирован. Эта проблема убивает рефакторинг, и, конечно, такой код полностью невосприимчив к вашим модульным тестам и вашим регрессионным тестам, если вы не настроите огромную мультиплатформенную тестовую ферму и, возможно, даже не тогда.
В этой области я обнаружил, что эта проблема приводит к ситуациям, когда рефакторизованное приложение тщательно тестируется и отправляется, только для получения немедленных отчетов об ошибках, которые приложение даже не будет компилировать на других платформах. Если код скрыт #ifdef
, и мы не можем его выбрать, мы не гарантируем, что он будет выглядеть как синтаксически корректный.
Отражающая сторона монеты заключается в том, что более продвинутые языки и методы программирования уменьшили необходимость условной компиляции в препроцессоре:
-
Для некоторых языков, таких как Java, все зависящие от платформы коды находятся в реализации JVM и связанных библиотек. Люди пошли на огромные расстояния, чтобы создать JVM и библиотеки, которые не зависят от платформы.
-
На многих языках, таких как Haskell, Lua, Python, Ruby и многие другие, дизайнеры столкнулись с некоторыми проблемами, чтобы уменьшить количество зависимых от платформы кода по сравнению с C.
-
На современном языке вы можете поместить код, зависящий от платформы, в отдельный блок компиляции за скомпилированным интерфейсом. Многие современные компиляторы имеют хорошие возможности для наложения функций через границы интерфейса, так что вы не платите много (или любого) штрафа за этот вид абстракции. Это не относится к C, потому что (a) нет отдельных скомпилированных интерфейсов; модель отдельной компиляции предполагает #include
и препроцессор; и (b) компиляторы C достигли совершеннолетия на машинах с 64 КБ кодового пространства и 64 КБ пространства данных; компилятор, достаточно изощренный для встроенных границ модулей, был почти немыслим. Сегодня такие компиляторы являются рутинными. Некоторые продвинутые компиляторы динамически строят и специализируют методы.
Резюме: используя языковые механизмы, а не текстовую замену, чтобы изолировать платформозависимый код, вы выставляете весь свой код компилятору, все проверяется как минимум, и у вас есть шанс сделать что-то вроде статического анализа, чтобы обеспечить подходящее покрытие. Вы также исключаете целую кучу методов кодирования, которые приводят к нечитаемому коду.
Ответ 3
Потому что современные компиляторы достаточно умны, чтобы в любом случае удалить мертвый код, поэтому вручную не нужно компилировать компилятор. То есть вместо:
#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 и С++ я бы никогда не делал этого - я бы использовал препроцессор, если бы не что-то еще, что он сразу же очистил читателя моего кода, что его кусок не является " Предполагалось, что они соблюдаются при определенных условиях. Я говорю, однако, что поэтому многие языки избегают препроцессора.
Ответ 4
Другие языки поддерживают эту функцию, используя общий препроцессор, такой как m4.
Действительно ли мы хотим, чтобы на каждом языке была реализована его реализация с заменой текста перед выполнением?
Ответ 5
Обратите внимание, что макросы/предварительная обработка/условные выражения /etc обычно рассматриваются как функция компилятора/интерпретатора, в отличие от языковой функции, поскольку они обычно полностью независимы от формального определения языка и могут варьироваться от компилятора к реализации компилятора для тот же язык.
Ситуация на многих языках, где директивы условной компиляции могут быть лучше, чем код времени выполнения 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 независимо от того, является ли debug истинным.
Ответ 6
Препроцессор C может быть запущен в любом текстовом файле, ему не нужно C.
Конечно, если вы запускаете на другом языке, он может символизировать странные способы, но для простых структур блоков, таких как #ifdef DEBUG, вы можете поместить их на любой язык, запустить на нем предварительный процессор C, а затем запустить свой язык специфический компилятор на нем, и он будет работать.
Ответ 7
Многие современные языки фактически имеют синтаксические возможности метапрограммирования, выходящие за пределы CPP. Практически все современные Lisps (Arc, Clojure, Common Lisp, Scheme, newLISP, Qi, PLOT, MISC,...), например, имеют чрезвычайно мощные (Turing-complete, фактически) макросистемы, так почему они должны ограничить себя жесткими макросами стиля CPP, которые не являются даже реальными макросами, просто текстовыми фрагментами?
Другие языки с мощным синтаксическим метапрограммированием включают Io, Ioke, Perl 6, OMeta, Converge.
Ответ 8
Лучше спросить, почему C прибегнул к использованию предварительного процессора для реализации этих видов задач метапрограммирования? Это не такая особенность, как компромисс с технологией того времени.
Препроцессорные директивы на C были разработаны в то время, когда машинные ресурсы (скорость процессора, ОЗУ) были скудными (и дорогими). Препроцессор предоставил возможность реализовать эти функции на медленных машинах с ограниченной памятью. Например, у первой машины, которой я когда-либо владел, было 56 КБ ОЗУ и 2 МГц процессора. Он по-прежнему обладал полным компилятором K & R C, который ограничивал системные ресурсы, но был работоспособен.
Более современные языки используют сегодня более мощные машины, чтобы обеспечить лучшие способы обработки разновидностей задач метапрограммирования, с которыми работал препроцессор.
Ответ 9
Другие языки также имеют лучшую динамическую привязку. Например, у нас есть код, который мы не можем отправить некоторым клиентам по причинам экспорта. Наши библиотеки "C" используют инструкции #ifdef
и разрабатывают трюки Makefile (что почти то же самое).
В коде Java используются плагины (ala Eclipse), поэтому мы просто не отправляем этот код.
Вы можете сделать то же самое в C с помощью общих библиотек... но препроцессор намного проще.
Ответ 10
Поскольку уменьшение размера двоичного файла:
- Может быть сделано другими способами (сравните средний размер исполняемого файла С++ с исполняемым файлом С#, например).
- Разве это не так важно, когда вы его взвешиваете, не имея возможности писать программы, которые действительно работают.