Форум «Всё о Паскале» _ FAQ _ Директивы компилятора
Автор: Altair 28.01.2005 11:34
Директивы компилятора Почему-то мало кто пользуется директивами компилятора в полном масштабе... Еще {$M} {$N} {$E} {$I} приходится видеть, но вот такие: {$ifdef}...{$else}...{$endif} - почти никогда; несмотря на то, что они открывают довольно широкие возможности для программиста, а именно: позволяют писать код, успешно компилируемый на разных компиляторах Паскаля, в том числе и 32 битных, включая FPC и TMT.
Еще одно удобство на мой взгяд - широкие возможности при отладке приложений.
Общий синтаксис этих команд таков: {$define <условный_символ>} - установить условный_символ. {$ifdef <условный_символ>} - код следующий после этой директивы компилируется только в том случае, если условный_символ был установлен. {$else} - эта директива обозначает начало альтернативного участка кода. {$endif} - ограничивает действие директив {$ifdef} и {$else}.
Режимы компиляции. Отладка. Предположим, что в программе необходимо на этапе отладки выводить какие-то данные для тестирования алгоритма... Можно, конечно, в окончательном варианте кода закомментировать такие отладочные выводы, или удалить, но лучше заключить "отладочные операторы" в такую конструкцию, с использованием директив:
Код
{$define debug} var x:byte; begin x:=1; inc(x); {$ifdef debug} writeln(x); {$endif} end.
Таким образом, при работе программы на экран будет выведено значение "2". Если отладку надо отменить, мы можем убрать символ "$" из директивы {$define debug} - и теперь это будет просто коментарий, и следовательно на экран ничего не будет выведено.
Универсальность кода Хорошо, когда мы берем старый код из TP и он компилируется, и программа правильно работает в компиляторе постарше... А почему бы не реализовать совместимость наоборот или напрямую? Часто при компиляции, возникают ошибки только из-за того, что код оптимизирован под конкретный компилятор, например под BP, и в TP отказывается компилироваться... Выходом из такой ситуации является правильное написание кода, а именно отладка кода под различные компиляторы (конечно если это требуется). Для примера рассмотрим программу для построения графиков некоторых функций (в полярной системе координат) http://pascal.dax.ru/forum/oleg_z/prog/elgr.zip
Procedure Graphic; Function Ro(Fi:Double):Double; begin case g of 1:ro:=fi; {бЇЁа «м аеЁ¬Ґ¤ } 2:ro:=Sin(7*fi); {ᥬЁ«ҐЇҐбвЄ®ў п ஧ } 3:ro:=1-cos(fi); {Є а¤Ё®Ё¤ } 4:ro:=5*cos(fi)+2; {г«ЁвЄ Џ бЄ «п} 5:if cos(2*fi)>=0 then ro:=sqrt(2*cos(2*fi)); {‹Ґ¬ЁбЄ в ЃҐаг««Ё} end; end; Const c=2*pi; step=0.0005; r=220; {$ifdef Win32} x0=400; y0=300; {$else} x0=320; y0=220; {$endif} var k,max,fi:Double; Begin fi:=0; max:=0.0001; repeat if max<abs(ro(fi)) then max:=abs(ro(fi)); fi:=fi+0.01 until fi>c;
fi:=0; repeat k:=ro(fi)/max; if k<1 then putpixel(x0+Round(r*cos(a*fi)*k), y0+Round(r*sin(b*fi)*k),15); If Keypressed then Exit else fi:=fi+step until fi>c end; {graphic}
Этот код можно скомпилировать вот в этих компиляторах: FPC(target: Win32, DOS), TP7, BP7, BPW (target:real, protected mode), TMT. Неплохо, правда? И везде он будет работать одинаково правильно! Конечно при компиляции в FPC под Win32, это будет windows приложение, а в остальных случаях консольное DOS (16 битное) приложение... Прокоментирую первые строчки программы (а дальше я думаю все понятно)
Код
{$ifdef Win32} {- если компилируем под Win32 } { (это срабтает только в FPC, TMT)} {$APPTYPE GUI} {- режим компиляции: графическое windows приложение.} {$else} {- компилируем в 16 битных компиляторах.} {$E+} {$N+} {-включаем эмуляцию сопсроцессора, т.к. в } { программе используются тип double} {$endif} {-конец условного участка кода.} Uses {$ifdef Win32} {-если компилируем под Win32 } WinCrt,Windows,{ подключаем соотв. модули.} {$else} {- иначе компиляция в 16 битных компиляторах} CRT, {$endif} {- конец условного куска кода.} Graph; {- а этот модуль подключается в любом случае.}
Эмуляция сопроцессора Очень часто у многих возникает вопрос - почему при компиляции у меня возникает ошибка Error 116: Must be in 8087 mode to compile this. Ответ: Вы используете один из следующих вещественных типов: Single, Double, Extended, Comp Для работы с этими типами, необходима эмуляция сопроцессора. Просто добавьте в начало программы директивы: {$E+, $N+}
Оптимизация Для уменьшения размера программ желательно добавить следующие директивы: {$B-} - быстрое вычисление логических условий;
Но с оптимизацией будьте очень осторожны !! Например, вот такой пример будет компилироваться, но выдаст неверный результат (попробуйте запустить программу без ключей {$B-} и {$B+}...):
Код
var x, y: integer;
function do_it: boolean; begin x := 5; { Здесь меняется значение глобальной переменной } do_it := true; end;
begin x := 2; {$B-} if (x > 5) and do_it then y := x else y := 2 * x; {$B+}
{ При "быстром" вычислении логических условий результат будет неверный } writeln('y = ', y); writeln('x = ', x) end.
Добавлено: Volvo
{$D-} - отключить информацию для отладки (пропадает возможность отлаживать программу!!! (через F7)). При этом размер сократится на 100-150 байт...
Описание других директив
{$N+} - сопроцессор {$E+} - эмуляция сопроцессора {$I-} - отключение проверок ввода/вывода {$R-} - отключение проверок на границы массивов {$S-} - отмена проверки на переполнение стека {$Q-} - отмена проверок на границы типов (overflow, underflow)
Автор: volvo 29.01.2005 6:10
Директивы компилятора (продолжение)
Директивы без параметров:
{$A+} - Выравнивание данных. (Глобальная директива). Эта директива позволяет переключаться между выравниванием переменных и типизированных констант по границе слова (состояние {А+}) и по границе байта (состояние {A-}). При выравнивании на границу слова адресация происходит за 1 цикл обращения к памяти вместо двух.
{$Е+} - Эмуляция математического сопроцессора. (Глобальная директива). Разрешает ({$E+}) или запрещает ({$E-}) компоновку с библиотекой, которая будет эмулировать работу сопроцессора в случае его отсутствия. При компиляции в режиме {$N+, E+} Паскаль выполняет компоновку с полным эмулятором сопроцессора. Полученный EXE файл может выполняться на любой машине независимо от присутствия сопроцессора. В состоянии {$N+, E-} компоновка осуществляется с гораздо меньшей по размеру библиотекой, которая может использоваться только при наличии сопроцессора. (Эта директива не производит никаких действий при использовании ее в модуле. Она действует только при компоновке программы)
{$F-} - Выбор модели вызова (Far/Near). (Локальная директива). Управляет выбором типа вызова компилируемых процедур и функций. В процессоре поддерживается два типа вызовов и инструкций возврата управления - ближние (NEAR) и дальние (FAR). Ближние вызовы передают управление другой ячейке в пределах того же программного сегмента, а дальние вызовы позволяют перейти в другой программный сегмент.
Инструкция ближнего обращения CALL помещает в стек 16-битовый адрес возврата (только смещение), а инструкция дальнего вызова помещает в стек 32-битовый адрес возврата (адрес сегмента и смещение). Соответствующая инструкция RET извлекает из стека только смещение или адрес сегмента и смещение.
При компиляции в режиме {$F+} все процедуры и функции всегда используют дальнюю модель вызова (Far Call). При указании директивы {$F-} компилятор Паскаля автоматически выбирает необходимый тип обращений: дальний, если процедура или функция описывается в интерфейсном разделе модуля (с расчетом на вызов из других блоков), и ближний в случае описания в разделе Implementation. Существует несколько случаев, когда требуется использовать директиву {$F+}. Например, при использовании оверлейных модулей и при передаче процедур/функций в качестве параметров они должны быть откомпилированы с расчетом на дальнюю модель вызова.
{$I+} - Режим контроля ввода/вывода. (Локальная директива). Этот ключ задает ({$I+}) или отменяет ({$I-}) генерацию кода, проверяющего результат выполнения операций ввода/вывода. Если операция ввода/вывода не может завершиться корректно и включен режим {$I+}, происходит ошибка времени выполнения (Run-time Error). При отключенном контроле (режим {$I-}) аварийного останова программы не происходит, и результат операции может быть проанализирован с помощью функции IOResult. Внимание: после первого обращения к IOResult, флаг ошибки сбрасывается, и проанализировать ошибку во второй раз не получится... Чтобы это сделать, нужно сохранить значение IOResult в какой-либо переменной, и проверять именно значение этой переменной !!!
{$N-} - Использование сопроцессора. (Глобальная директива). При указании режима {$N-} генерируется код для программного выполнения всех вещественных вычислений. При режиме {$N+} генерируется код для выполнения таких вычислений аппаратно с помощью сопроцессора.
{$R-} - Режим проверки границ. (Локальная директива). Приводит в действие ({$R+}) или отменяет ({$R-}) генерирование кода проверки границ. При указании {$R+} все выражения со строками и массивы проверяются на нахождение индекса в допустимых пределах (все операторы присваивания переменным скалярных типов проверяются на принадлежность границам типа). При нарушении диапазона происходит ошибка времени исполнения. Эта директива увеличивает размер программы и уменьшает скорость ее исполнения. Поэтому желательно использовать режим {$R+} при отладке, выключая его в окончательной версии.
{$S+} - Режим проверки переполнения стека. (Локальная директива). Приводит в действие ({$S+}) или отменяет ({$S-}) генерирование кода проверки границ. При указании {$S+} генерируется код, проверяющий, достаточно ли места в стеке выделено для локальных переменных. При недостатке места в стеке происходит ошибка времени исполнения. В режиме {$S-} наиболее вероятно произойдет "зависание" системы.
{$V+} - Режим проверки параметров строкового типа. (Локальная директива). В состоянии {$V+} выполняется строгая проверка типа, прикоторой требуется, чтобы формальный и фактический параметр имели иденимчные строковые типы. В состоянии {$V-} ф качастве фактического параметра допускается использовать любую переменную строкового типа, даже если ее описанная длинна не совпадает с длиной соответствующего формального параметра.
Директивы с параметрами:
{$L имя_файла} - Компоновка файла объектных кодов. Данная директива предписывает компилятору скомпоновать указанный файл с компилируемой программой или модулем. Директива {$L имя_файла} используется для компоновки кода, написанного на ассемблере.
Кроме линковки с OBJ файлами данная директива очень часто используется для включения BGI драйверов и шрифтов в EXE файл (для полностью автономной работы программы). Для того чтобы произвести эту операцию нужно сделать следующее:
1. С помощью утилиты BINOBJ.EXE (входящей в дистрибутив Турбо Паскаля) преобразовать BGI файл в OBJ-формат (работа с утилитой BINOBJ осуществляется из командной строки):
Сама инициализация графики (например, режима VGA) будет осуществляться так:
Код
... Var grDriver, grMode, ErrCode : Integer; ... Begin If RegisterBGIDriver(@EGAVGADriverProc) < 0 Then Begin WriteLn('Error registering driver : ', GraphErrorMsg(GraphResult)); Halt(100) End;
grDriver := VGA; grMode := VGAHi; InitGraph(grDriver, grMode, ''); ErrCode := GraphResult; If ErrCode <> grOk Then Begin WriteLn('Graphics error : ', GraphErrorMsg(ErrCode)); Halt(100) End; ... End.
После этого можно не заботиться о наличии в текущей директории соответствующего BGI драйвера.