LG.BALUKATION's Weblog

Ничего, это тоже кое-что… А при желании из него можно сделать что угодно

Препроцессор

Posted by LG.BALUKATION на 2008/12/09

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

Этот весьма своеобразный инструмент издавна помогает людям писать разнообразный софт. В «новых языках» его значение стремятся принизить, но есть языки и случаи где он просто необходим.

Ну вот например C/C++ и ассемблер весьма активно пользуются препроцессором, а в Паскале и Жаве его используют реже. Некоторые адепты Жавы вообще считают, что препроцессора там нет, однако у мобильной редакции (Java Micro Edition — J2ME) он вполне активно используется.

Что же это такое и почему оно так важно?

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

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

Например, можно просто заменять один набор символов другими символами — в былые времена так часто делали константы, просто указывая, что скажем строчка «ZERO» будет заменятся на символ ‘0’. После такого указания, препроцессор станет подменять все встреченные последовательности символов «ZERO» на нолики. Однако, ко времени C++ было решено считать этот путь тёмной магией и для констант появилась специальная языковая конструкция.

Можно делать и более изощрённые замены, именуемые макросами… Дело в том, что макрос может обладать параметрами — например можно заменять «SQR(a)» на «a * a» или лучше «(a) * (a)». Этот метод тоже уже признан не самым правильным и заместо него появились такие штуки как шаблоны и встраиваемые (inline) функции.

Третья фича препроцессора заключается в возможности вставить в один файл содержимое другого. Ну например мы пишем некий модуль, который содержит в себе набор функций, доступных для вызова из других модулей. Что бы другие модули могли использовать наш, мы должны им рассказать, чегож за прекрасные (или не очень прекрасные…) функции есть в нашем модуле. По-этому мы оформляем перечень этих функций в отдельном файле, что бы другие модули смогли включить в себя описание наших функций и пользоваться ими почти как родными. Конечно, можно и не делать отдельного заголовка (header) и включать сразу весь модуль с описанием и кодами функций, но это не всегда приемлемо. Например исходного кода может и не быть, потому как некоторые распространяют уже собранные модули и тогда доступ к ним можно получить через включение (include) заголовка. На самом деле это весьма полезная и интересная технология, например так можно удобно разделять большие модули на несколько файлов. В С/С++ вообще принято писать объявления и реализацию в разных файлах, хотя скажем Жава смешивает это всё в один файл и там редко такое используется. Но там и механизм иной есть для доступа к другим модулям…

Четвёртой основной фишкой препроцессора является так называемая «условная компиляция». Суть её сводится к комментированию (или не комментированию…) некоторых областей кода в зависимости от определённых условий. Поскольку после препроцессора код попадает обычно прямиком компилятору, так можно «выкинуть» некоторые лишние действия. Например мы пишем программу, у которой будет несколько опциональных штук. Ну скажем пусть будет некая «простая версия», которой хватит только на вывод фразы «Здравствуйте!», и «падонкафаская», печатающая уже «ПрЕвЕд!11». Да, пример явно попахивает сферичностью, однако вполне сойдёт. Важной особенностью тут является то, что в падонкафской нафиг не нужно «обычное» приветствие и наоборот — так что реализовать обе штуки в одной программе нельзя, а писать две почти одинаковые программы слишком лениво. Не плохим вариантом является написание одной программы, часть которой будет меняться при сборке с разными параметрами. Вот для этого можно и воспользоваться условной компиляцией — сделать чтоб падонкафская версия содержала в себе одну строку, а «обычная» другую, ведь в остальном функционал-то одинаковый!

Вот вроде и все основные фичи препроцессора — автозамена, макросы (автозамена с параметрами), включение одних файлов в другие и условная компиляция. Кстати именно из-за последней я и подумал написать этот пост.

Столкнулся недавно на работе с взаимным недопониманием меня с одной стороны и Visual C++ 2008 с другой. Неразбериха возникла в месте, суть которого сводится к «если 1 — собирай код 1, иначе если 2 — собирай код 2, иначе если 3…» (ifdef — elif — elif — … — else — endif). Этот кусок никоу не был нужен, посему там всегда срабатывал последний случай по-умолчанию и вся эта кутерьма условий просто отбрасывалась. Однако, в пятницу я решил, что действие одного из «серединных» условий вполне подходит для того, что я делаю ну и прописал соответственно чтоб оно включалось…

Но студия не осилила :-? Она скромно сказала, что ничего в этих препроцессорных штучках не понимает и посему собрать не сумела. Вот блин жесть — последнее оно спокойно хавает, а из середины уже не может :-( Некогда ещё в Жаве я с подобным сталкивался и решил попробовать заменить elif на elifdef. Студия перестала ругаться и собрала игру…

А вот сегодня я продолжил играться с тем куском и получил отказ в сборке с первым условием. Последнее может, средние тоже может, а первое не понимает. Опять двадцать-пять! Ещё ко мне закрались мои же сомнения насчёт доступности elifdef в C++ и я решил обратится к первоисточнику. К счастью, у меня есть PDF’ки стандартов C99 и C++98, в них я и глянул — нет такой директивы, только elif есть!

Проблема кроется в разнице между директивами if и ifdef. Последняя принимает лишь символ препроцессора и считается истинной если такой символ объявлен (define). А вот первая принимает некое логическое условие — а ведь просто указание символа это ещё нифига не условие! Для проверки на объявленность символа есть операция defined — она возвращает истину когда условие обьявлено. Т. е. получается, что ifdef это краткая запись if defined, но это всёж разные штуки и лучше их не смешивать. В оригинале же кода была смесь из ifdef и elif без defined. Вроде как понадвписывал дефайнед и терь собирает любой вариант…

А вот почему собирало с elifdef я не совсем понимаю — ведь символа такого быть не должно, соответственно всё должо красиво падать с чем-нить вроде «fatal error такая-то — бла-бла-бла» %) Хотя, пиши я код в студии, мож и увидел бы подвох по какой-нить странной подсветке, но я правлю код не в студии…

Реклама

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s