$Optimization — Директива компилятора Delphi


Содержание

Директивы компилятора

директивы условной компиляции

<$C+>и <$C->— директивы проверки утверждений
<$I+>и <$I->— директивы контроля ввода/вывода
<$M>и <$S>— директивы, определяющие размер стека
<$M+>и <$M->— директивы информации времени выполнения о типах
<$Q+>и <$Q->— директивы проверки переполнения целочисленных операций
<$R>— директива связывания ресурсов
<$R+>и <$R->— директивы проверки диапазона
<$APPTYPE CONSOLE>— директива создания консольного приложения

1) Директивы компилятора, разрешающие или запрещающие проверку утверждений

По умолчанию <$C+>или

Область действия локальная

Директивы компилятора $C разрешают или запрещают проверку утверждений. Они влияют на работу процедуры Assert,используемой при отладке программ. По умолчанию действует

директива <$C+>и процедура Assert генерирует исключение EAssertionFailed, если проверяемое утверждение ложно.

Так как эти проверки используются только в процессе отладки программы, то перед ее окончательной компиляцией следует указать директиву <$C->. При этом работа процедур Assert будет блокировано и генерация исключений EassertionFailed производиться не будет.

Директивы действуют на весь файл исходного кода независимо от того, в каком месте файла они расположены.

2) Директивы компилятора, включающие и выключающие контроль файлового ввода-вывода

По умолчанию <$I+>или

Область действия локальная

Директивы компилятора $I включают или выключают автоматический контроль результата вызова процедур ввода-вывода Object Pascal. Если действует директива <$I+>, то при возвращении процедурой ввода-вывода ненулевого значения генерируется исключение EInOutError и в его свойство errorcode заносится код ошибки. Таким образом, при действующей директиве <$I+>операции ввода-вывода располагаются в блоке try. except, имеющем обработчик исключения EInOutError. Если такого блока нет, то обработка производится методом TApplication.HandleException.

Если действует директива <$I->, то исключение не генерируется. В этом случае проверить, была ли ошибка, или ее не было, можно, обратившись к функции IOResult. Эта функция очищает ошибку и возвращает ее код, который затем можно анализировать. Типичное применение директивы <$I->и функции IOResult демонстрирует следующий пример:

В этом примере на время открытия файла отключается проверка ошибок ввода вывода, затем она опять включается, переменной i присваивается значение, возвращаемое функцией IOResult и, если это значение не равно нулю (есть ошибка), то предпринимаются какие-то действия в зависимости от кода ошибки. Подобный стиль программирования был типичен до введения в Object Pascal механизма обработки исключений. Однако сейчас, по-видимому, подобный стиль устарел и применение директив $I потеряло былое значение.

3) Директивы компилятора, определяющие размер стека

Область действия глобальная

Локальные переменные в процедурах и функциях размещаются в стеке приложения. При каждом вызове процедуры или функции ее локальные переменные помещаются в стек. При выходе из процедуры или функции эти локальные процедуры удаляются из стека. Директивы компилятора $M задают параметры стека приложения: его минимальный и максимальный размеры. Приложение всегда гарантированно имеет размер стека, равный его минимальной величине. Если при запуске приложения Windows обнаруживает, что не может выделить этот минимальный объем памяти, то выдается сообщение об этой ошибке.

Если во время работы выясняется, что минимального размера стека не хватает, то размер увеличивается на 4 K, но не более, чем до установленного директивой максимального размера. Если увеличение размера стека невозможно из-за нехватки памяти или из-за достижения его максимальной величины, генерируется исключение EStackOverflow. Минимальный размер стека по умолчанию равен 16384 (16K). Этот размер может изменяться параметром minstacksize директивы <$M>или параметром number директивы <$MINSTACKSIZE>.

Максимальный размер стека по умолчанию равен 1,048,576 (1M). Этот размер может изменяться параметром maxstacksize директивы <$M>или параметром number директивы <$MAXSTACKSIZE number>. Значение минимального размера стека может задаваться целым числом в диапазоне между1024 и 2147483647. Значение максимального размера стека должно быть не менее минимального размера и не более 2147483647. Директивы задания размера стека могут включаться только в программу и не должны использоваться в библиотеках и модулях.

В Delphi 1 имеется процедура компилятора <$S>, осуществляющая переключение контроля переполнения стека. Теперь этот процесс полностью автоматизирован и директива <$S>оставлена только для обратной совместимости.

4) Директивы компилятора, включающие и выключающие генерацию информации времени выполнения о типах (runtime type information — RTTI)

По умолчанию <$M->или

Область действия локальная

Директивы компилятора $I включают или выключают генерацию информации времени выполнения о типах (runtime type information — RTTI). Если класс объявляется в состоянии <$M+>или является производным от класса объявленного в этом состоянии, то компилятор генерирует RTTI о его полях, методах и свойствах, объявленных в разделе published. В противном случае раздел published в классе не допускается. Класс TPersistent, являющийся предшественником большинства классов Delphi и все классов компонентов, объявлен в модуле Classes в состоянии <$M+>. Так что для всех классов, производных от него, заботиться о директиве <$M+>не приходится.

5) Директивы компилятора, включающие и выключающие проверку переполнения при целочисленных операциях

По умолчанию <$Q->или

Область действия локальная

Директивы компилятора $Q включают или выключают проверку переполнения при целочисленных операциях. Под переполнением понимается получение результата, который не может сохраняться в регистре компьютера. При включенной директиве <$Q+>проверяется переполнение при целочисленных операциях +, -, *, Abs, Sqr, Succ, Pred, Inc и Dec. После каждой из этих операций размещается код, осуществляющий соответствующую проверку. Если обнаружено переполнение, то генерируется исключение EIntOverflow. Если это исключение не может быть обработано, выполнение программы завершается.

Директивы $Q проверяют только результат арифметических операций. Обычно они используются совместно с директивами <$R>, проверяющими диапазон значений при присваивании. Директива <$Q+>замедляет выполнение программы и увеличивает ее размер. Поэтому обычно она используется только во время отладки программы. Однако, надо отдавать себе отчет, что отключение этой директивы приведет к появлению ошибочных результатов расчета в случаях, если переполнение действительно произойдет во время выполнении программы. Причем сообщений о подобных ошибках не будет.

6) Директива компилятора, связывающая с выполняемым модулем файлы ресурсов

Область действия локальная

Директива компилятора <$R>указывает файлы ресурсов (.DFM, .RES), которые должны быть включены в выполняемый модуль или в библиотеку. Указанный файл должен быть файлом ресурсов Windows. По умолчанию расширение файлов ресурсов — .RES. В процессе компоновки компилированной программы или библиотеки файлы, указанные в директивах <$R>, копируются в выполняемый модуль. Компоновщик Delphi ищет эти файлы сначала в том каталоге, в котором расположен модуль, содержащий директиву <$R>, а затем в каталогах, указанных при выполнении команды главного меню Project | Options на странице Directories/Conditionals диалогового окна в опции Search path или в опции /R командной строки DCC32.

При генерации кода модуля, содержащего форму, Delphi автоматически включает в файл .pas директиву <$R *.DFM>, обеспечивающую компоновку файлов ресурсов форм. Эту директиву нельзя удалять из текста модуля, так как в противном случае загрузочный модуль не будет создан и сгенерируется исключение EResNotFound.

7) Директивы компилятора, включающие и выключающие проверку диапазона целочисленных значений и индексов

По умолчанию <$R->или

Область действия локальная

Директивы компилятора $R включают или выключают проверку диапазона целочисленных значений и индексов. Если включена директива <$R+>, то все индексы массивов и строк и все присваивания скалярным переменным и переменным с ограниченным диапазоном значений проверяются на соответствие значения допустимому диапазону. Если требования диапазона нарушены или присваиваемое значение слишком велико, генерируется исключение ERangeError. Если оно не может быть перехвачено, выполнение программы завершается.

Проверка диапазона длинных строк типа Long strings не производится. Директива <$R+>замедляет работу приложения и увеличивает его размер. Поэтому она обычно используется только во время отладки.

8) Директива компилятора, связывающая с выполняемым модулем файлы ресурсов

Область действия локальная

Директива компилятора <$R>указывает файлы ресурсов (.DFM, .RES), которые должны быть включены в выполняемый модуль или в библиотеку. Указанный файл должен быть файлом ресурсов Windows. По умолчанию расширение файлов ресурсов — .RES. В процессе компоновки компилированной программы или библиотеки файлы, указанные в директивах <$R>, копируются в выполняемый модуль. Компоновщик Delphi ищет эти файлы сначала в том каталоге, в котором расположен модуль, содержащий директиву <$R>, а затем в каталогах, указанных при выполнении команды главного меню Project | Options на странице Directories/Conditionals диалогового окна в опции Search path или в опции /R командной строки DCC32.

При генерации кода модуля, содержащего форму, Delphi автоматически включает в файл .pas директиву <$R *.DFM>, обеспечивающую компоновку файлов ресурсов форм. Эту директиву нельзя удалять из текста модуля, так как в противном случае загрузочный модуль не будет создан и сгенерируется исключение EResNotFound.

Компилятор Delphi выполняет оптимизацию?

Я использую Delphi 7 IDE. Оптимизирует ли компилятор Delphi коды так же, как это делает компилятор C ++ в следующей ссылке?

Если ZeroMemory был вызван в этом примере вместо SecureZeroMemory , компилятор мог бы оптимизировать вызов, потому что буфер szPassword не читается, прежде чем он выходит из области видимости. Пароль будет оставаться в стеке приложений, где он может быть захвачен в аварийном дампе или проверен вредоносным приложением.

5 ответов

Да, конечно, Delphi выполняет оптимизацию. Однако он не выполняет оптимизацию, SecureZeroMemory должна обойти функция SecureZeroMemory . Нет необходимости использовать эту функцию в Delphi; просто используйте старую старую ZeroMemory или даже FillChar . Они не являются макросами и не делают ничего, что Delphi распознает как неиспользуемые операторы присваивания, которые можно оптимизировать.

Delphi выполняет оптимизацию кода по умолчанию, вы можете отключить ее в Project> Options> Compiler .

Справка Delphi содержит несколько советов о том, какой тип оптимизации используется:

Директива $ O управляет оптимизацией кода. В состоянии <$ O +>компилятор выполняет ряд оптимизаций кода, таких как размещение переменных в регистрах ЦП, устранение общих подвыражений и генерация индукционных переменных.

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

Я не верю, что компилятор когда-нибудь удалит явно мертвый код, подобный этому. У меня никогда не было проблем с установкой точек останова в коде, который можно было бы исключить как избыточный.

Для некоторых сценариев компилятор может определить, недоступен ли код, и устранить его.

Например, компилятор правильно удаляет «недоступную» часть кода ниже.
Он не будет генерировать код для этой строки, поэтому:

  1. Таким образом, нет синих пуль, указывающих на наличие кода
  2. Точки останова, установленные на этой линии, будут помечены как «недостижимые».

Только что протестирован в Delphi XE, но более старые версии Delphi ведут себя аналогично.

Требуется некоторое время, чтобы понять, когда (или когда нет) сработает оптимизатор на уровне кода и на том же уровне.

Например: если у вас включена оптимизация, компилятор также удалит переменные, как только они не будут использованы.
Иногда это даже устраняет глобальные символы. Дэнни Торп (бывший инженер компилятора Delphi и главный научный сотрудник) однажды написал магический метод Touch, который предотвращает это.
Просто вызовите этот метод Touch в конце вашего метода, чтобы обмануть оптимизатор во время отладки:

Delphi, безусловно, оптимизирует код (это современный и отличный компилятор). Еще один пример оптимизации удаления строк:

Я хотел бы убедиться, что оптимизация находится в правильном состоянии (и не случайно включена или выключена), добавив <$I MyProjectOptions.inc>в каждый файл .pas в моем проекте. Это идет чуть ниже имени устройства (справа вверху файла). В «MyProjectOptions.inc» вы просто добавляете этот код:

Наконец, убедитесь, что вы определили «DEBUG» и «NDEBUG» (или ваш эквивалент в более старых версиях Delphi) в разделе «Условные определения» в Project> Options> Diectories / Conditionals.

Delphi. Условная компиляция. Краткий справочник

Условная компиляция предназначена для управления компиляцией программного кода. В Delphi имеется несколько способов её реализации, но в основном используются только четыре.

В этой реализации условная директива <$IFDEF …>проверяет, был ли определён указанный в ней символ условной компиляции. Если да, то код программы между директивами <$IFDEF …>и <$ENDIF>компилируется. В противном случае, код между этими двумя директивами компилироваться не будет.

Эта реализация отличается от предыдущей наличием директивы <$ELSE>. В этом случае, если условный оператор определён, будет компилироваться код программы между директивами <$IFDEF>и <$ELSE>. В противном случае, будет компилироваться код программы между директивами <$ELSE>и <$ENDIF>.

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

Как определить символ условной компиляции? Для этого также существует два способа.

Первый – использование директивы

Второй способ. Определить его в свойствах проекта. Для этого в меню Project нужно выбрать пункт Options и в поле Conditional defines ввести символ условной компиляции.

Третий и четвёртый способы являются аналогией первого и второго соответственно. Отличие в том, что используется условная директива <$IFNDEF>. Эта директива проверяет, что указанный символ не определён. То есть, является прямой противоположностью директивы <$IFDEF>.

В случае <$IFNDEF>, код между этой директивой и <$ENDIF>либо <$ELSE>будет скомпилирован, только если указанный в ней символ условной компиляции не определён. В остальном, принцип реализации аналогичен приведённому для директивы <$IFDEF>:

В Delphi уже имеется ряд символов условной компиляции. Вот их «классический» набор.

Значение
MSWINDOWS Код компилируется только в ОС Windows
WIN32 То же самое, только код компилируется лишь для 32-разядной ОС.
LINUX Был введён в Delphi 6 вместе с MSWINDOWS для определения платформы, для которой необходимо компилировать тот или иной код.Так как компиляция для Linux в Delphi до сих пор отсутствует, то использование этого символа в программах не имеет смысла.
CONSOLE Консольное приложение
CPU386 Компиляция для процессоров типа i386 или более современных. Так как речь идёт о процессорах типа i386, использование данного символа уже давно потеряло актуальность.
CONDITIONAL EXPRESSIONS Проверяет использование директив $IF

Также в число «классических» входит и символ VERxxx, который указывает конкретную версию компилятора. По сути, это целое семейство символов условной компиляции, в которое с выходом новой версии Delphi добавляется новый символ соответствующий этой версии. Ниже представлена таблица соответствия версий Delphi символам условной компиляции VERxxx (материал взят с Исходники.ру).

Символ условной компиляции
1 VER80
2 VER90
3 VER100
4 VER120
5 VER130
6 VER140
7 VER150
8 VER160
2005 VER170
2006 VER180
2007* VER180 или VER185
2009 VER200
2010 VER210
XE VER220
XE2 VER230
XE3 VER240
XE4 VER250
XE5 VER260
XE6 VER270
XE7 VER280
XE8 VER290
10 Seatle VER300
10.1 Berlin VER310
10.2 Tokyo VER320

*-версии 2006 и 2007 совместимы между собой на уровне двоичного кода. Поэтому, для поддержки обеих версий следует использовать символ VER180. Если необходима поддержка только версии 2007, необходимо использовать символ VER185.

После выхода в 2011 году кроссплатформенной библиотеки FireMonkey, были введены новые символы условной компиляции для MacOS и iOS. А, в 2013 году с появлением поддержки Android был введён и специальный символ условной компиляции для этой ОС. Ниже представлен перечень этих символов.

Символ условной компиляции
MacOS MACOS
iOS IOS
Android ANDROID

Условная компиляция на примере кроссплатформенного приложения:

Does Delphi compiler perform optimization?

I am using Delphi 7 IDE. Does Delphi compiler optimize codes, just like what the C++ compiler is doing in this following link?

If ZeroMemory were called in this example instead of SecureZeroMemory , the compiler could optimize the call because the szPassword buffer is not read from before it goes out of scope. The password would remain on the application stack where it could be captured in a crash dump or probed by a malicious application.

5 Answers 5

Yes, of course Delphi performs optimizations. However, it does not perform the optimization that the SecureZeroMemory function is meant to circumvent. There is no need to use that function in Delphi; just use plain old ZeroMemory , or even FillChar . They’re not macros, and they don’t do anything that Delphi recognizes as being unused assignment statements that could get optimized out.

Delphi performs code optimization by default, you can disable it in Project > Options > Compiler.

The Delphi help provide a few tips of what type of optimizations are used:

The $O directive controls code optimization. In the <$O+>state, the compiler performs a number of code optimizations, such as placing variables in CPU registers, eliminating common subexpressions, and generating induction variables.

It also states that «the compiler performs no «unsafe» optimizations», but in the sense that they won’t alter the execution path, not from a security point of view.

I don’t believe the compiler will ever eliminate apparently dead code like this. I have never had trouble setting breakpoints on code that could have been eliminated as redundant.

For some scenarios, the compiler can detect if the code is unreachable and eliminate the code.

For instance, the compiler correctly eliminates the «unreachable» portion of the code below.
It will not generate code for that line so:

  1. So there are no blue bullets indicating there is code
  2. Breakpoints put on that line will be marked visually as ‘not reachable’

Just tested in Delphi XE, but older Delphi versions have similar behaviour.

It takes quite some while to learn when (or when not) the optimizer on code level and liker level kicks in.

For instance: When you have optimizations turned on, the compiler will also eliminate variables as soon as they are not used.
Sometimes, it even eliminates global symbols. Danny Thorpe (former Delphi Compiler engineer and Chief Scientist) once wrote a magic method Touch that prevents this.
Just call this Touch method at the end of your method to fool the optimizer during debugging:

Директивы компилятора

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

Давайте для примера рассмотрим несколько наиболее широко используемых директив компилятора.

Директива trace применяется при отладке программы для трассирования. Этот процесс немного похож на пошаговое выполнение императивной программы с отслеживанием значений переменных. Трассировка позволяет пользователю наблюдать за ходом выполнения программы. Если после ключевого слова trace указаны имена предикатов через запятую, то трассировка идет только по этим предикатам. В противном случае — по всем предикатам программы. После завершения отладки трассировку нужно выключить, чтобы компилятор мог осуществить оптимизацию хвостовой рекурсии, о которой рассказывалось в предыдущей лекции.

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

после слова «CALL» будет указано имя выполняемого предиката (текущая подцель) и его параметры; *

после слова «FAIL» будет выводиться имя текущей подцели, которая не была достигнута;

после слова «RETURN» будет выводиться результат вычисления текущей подцели, в случае успеха. При этом если у подцели есть еще альтернативы, к которым возможен возврат, то перед именем предиката высвечивается звездочка («*»);

слово «REDO» перед именем предиката указывает на то, что произошел возврат и происходит вычисление альтернативного решения.

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

Директива nowarnings используется для подавления предупреждения системы о том, что какая-то переменная встречается в предложении только один раз. Эту директиву стоит использовать только в хорошо отлаженных программах. Как правило, для подавления такого предупреждения («WARNING: The variable is only used once») достаточно заменить переменную, которая встретилась только один раз, на анонимную переменную.

С помощью директивы include при компиляции в исходный текст можно вставить содержимое некоторого файла.

Заметим, что многие директивы компилятора могут быть не только расположены в тексте программы, но и установлены в меню среды разработки Турбо Пролога (Options Compiler Directives). Значение директивы компилятора, указанное в тексте программы, имеет более высокий приоритет, чем значение, установленное в меню.

Раздел описания констант

Раздел, озаглавленный зарезервированным словом CONSTANTS, предназначен для описания констант. Объявление константы имеет вид:

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

Каждое определение константы должно размещаться в отдельной строке.

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

Разделов описания констант может быть несколько, но каждая константа должна быть определена до ее первого использования.

Раздел описания доменов

Раздел описания доменов является аналогом раздела описания типов в обычных императивных языках программирования и начинается с ключевого слова DOMAINS.

В Турбо Прологе имеются стандартные домены, которые не нужно указывать в разделе описания доменов. Основные стандартные домены — это:

integer — целое число (из промежутка -32768. 32767);

real — действительное число (лежащее между ±1e-307. ±1e308);

char — символ, заключенный в одиночные апострофы;

string — последовательность символов, заключенная в двойные кавычки;

symbol — символическая константа (начинающаяся со строчной буквы последовательность букв латинского алфавита, цифр и знаков подчеркивания или последовательность любых символов, заключенная в кавычки). Этот домен соответствует понятию атома, с которым мы познакомились во второй лекции;

file — файл (подробному изучению файлов будет посвящена лекция 12).

В разделе описания доменов объявляются любые нестандартные домены, используемые в качестве аргументов предикатов.

Объявление домена имеет следующий вид:

Удобно использовать описание доменов для сокращения имен стандартных доменов. Например, чтобы не писать каждый раз integer, можно написать следующее:

и далее использовать вместо ключевого слова integer односимвольное обозначение i.

Из доменов можно конструировать составные или структурные домены (структуры). Структура описывается следующим образом:

Каждая компонента структуры в свою очередь может быть структурой. Например, структура, описывающая точку на плоскости и имеющая две компоненты (координаты точки)

point = p(integer, integer)

может входить в качестве компоненты в более сложную структуру, описывающую треугольник:

triangle = tr(point, point, point)

В описание структуры могут входить альтернативы, разделенные символом «;» или ключевым словом «or».

Так, структуру, описывающую точку и на плоскости, и в пространстве, можно задать следующим образом:

point = p(integer, integer);p(integer, integer, integer).

Описание файлового домена имеет вид:

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

Например, список целых чисел описывается так:

Раздел описания предикатов внутренней базы данных

Работа с внутренними (динамическими) базами данных в Прологе будет рассмотрена в лекции 13. Начинается раздел описания предикатов внутренней базы данных с зарезервированного слова DATABASE и описываются в нем те предикаты, которые можно в процессе выполнения программы добавлять во внутреннюю базу данных или удалять оттуда. Описываются предикаты базы данных аналогично предикатам в разделе описания предикатов PREDICATES, который мы сейчас рассмотрим.

Раздел описания предикатов

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

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

Например, предикат, описывающий отношение «мама», которым мы пользовались в третьей лекции, может быть описан следующим образом:

Это описание означает, что у предиката два аргумента, причем оба строкового типа.

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

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

Кроме того, при описании предиката можно указать, будет он детерминированным или недетерминированным. Детерминированный предикат возвращает только одно решение, а недетерминированный предикат при помощи поиска с возвратом может давать много решений. Детерминированные предикаты менее требовательны к оперативной памяти и выполняются быстрее. Отсечение, с которым мы познакомились в третьей лекции, позволяет превращать недетерминированные предикаты в детерминированные.

Для того чтобы указать, что предикат является детерминированным (недетерминированным), нужно перед его именем поместить зарезервированное слово determ (nondeterm). Если ни determ, ни nondeterm при описании предиката не использовались, то, по умолчанию, предикат считается детерминированным.

В Турбо Прологе имеется директива компилятора check_determ, которая принудительно включает проверку предикатов на детерминированность.

В Турбо Прологе есть так называемые стандартные (встроенные) предикаты, которые не нужно описывать в разделе описания предикатов PREDICATES. Наиболее употребляемые из них мы рассмотрим чуть позже. Все встроенные предикаты являются детерминированными.

Раздел описания предложений Этот раздел можно считать основным разделом программы, потому что именно в нем содержатся факты и правила, реализующие пользовательские предикаты. Все предикаты, которые применяются в этом разделе и не являются стандартными предикатами, должны быть описаны в разделе описания предикатов или в разделе описания предикатов базы данных. Начинается этот раздел со служебного слова CLAUSES. Напомним, что предложения, у которых в заголовке указан один и тот же предикат, должны идти друг за другом. Такой набор предложений называется процедурой. Предложения довольно подробно обсуждались в третьей лекции, поэтому сейчас мы не будем подробно останавливаться на этом. Заметим только, что программу на Прологе принято оформлять по следующим правилам: между процедурами пропускается пустая строка; тело правила записывается со следующей строки, после строки, в которой был заголовок, с отступом; каждую подцель записывают на отдельной строке, одну под другой. Эти правила не являются обязательными, но они делают программу более «читабельной». Раздел описания внутренней цели С зарезервированного слова GOAL начинается раздел описания внутренней цели программы. Если этот раздел отсутствует, то после запуска программы Пролог-система выдает приглашение вводить вопросы в диалоговом режиме (внешняя цель). При выполнении внешней цели Пролог-система ищет все решения, выводя все возможные значения для переменных, участвующих в вопросе. Если же выполняется внутренняя цель, то осуществляется поиск только первого решения, а для получения всех решений нужно предпринимать дополнительные действия. Программа, компилируемая в исполняемый файл, который можно запускать независимо от среды разработки, обязательно должна иметь внутреннюю цель. Внешнюю цель обычно применяют на этапе отладки программы. Внешние и внутренние цели уже обсуждались в третьей лекции, они еще будут рассматриваться в следующей лекции, поэтому сейчас мы не будем останавливаться на этом вопросе более подробно. Пример. Запишем полностью реализацию на Турбо Прологе той базы знаний про мам и бабушек, которую мы рассматривали в качестве примера в третьей лекции. Нажимаем комбинацию клавиш Alt+E (от Editor), попадаем в редактор. Набираем код, приведенный ниже. DOMAINS /* раздел описания доменов */ s=string /* вводим синоним для строкового типа данных */ PREDICATES /* раздел описания предикатов */ mother(s,s) /* предикат мама будет иметь два аргумента строкового типа */ grandmother(s,s) /* то же имеет место и для предиката бабушка */ CLAUSES /* раздел описания предложений */ mother(«Наташа»,»Даша»). /* «Наташа» и «Даша» связаны отношением мама */ mother(«Даша»,»Маша»). /* «Даша» и «Маша» также принадлежат отношению мама */ grandmother(X,Y):- /* X является бабушкой Y, если найдется такой Z, что */ mother(X,Z), /* X является мамой Z, а */ mother(Z,Y). /* Z является мамой Y */ Для запуска программы нажимаем Alt+R (Run). Так как раздела описания внутренней цели в нашей программе не было, Пролог система выведет приглашение на ввод внешней цели («GOAL:»). Вводим вопросы, наблюдаем результаты. Повтор предыдущей цели F8. Теперь давайте рассмотрим некоторые наиболее употребляемые стандартные предикаты. Начнем с предикатов, предназначенных для вывода и ввода. Предикаты ввода-вывода Турбо Пролог имеет отдельные предикаты для чтения с клавиатуры или из файла данных целого, вещественного, символьного и строкового типа. Работе с файлами будет посвящена лекция 12, а сейчас мы рассмотрим чтение из стандартного устройства ввода информации (клавиатуры) и, соответственно, запись на стандартное устройство вывода информации (монитор). Предикат readln считывает строку с текущего устройства ввода и связывает ее со своим единственным выходным параметром. Предикат readint читает с текущего устройства целое число и связывает его со своим единственным выходным параметром. Предикат readreal отличается от предиката readint тем, что он считывает не целое, а вещественное число. Для чтения символа с текущего устройства ввода используется предикат readchar. Есть еще предикат inkey, который так же, как и readchar, читает символ со стандартного устройства ввода. Разница между ними в том, что предикат readchar приостанавливает работу программы до тех пор, пока не будет введен символ, а предикат inkey не прерывает выполнение программы. Если нужно просто проверить, нажата ли клавиша, можно воспользоваться предикатом keypressed, не имеющим аргументов. Предикат readterm предназначен для чтения сложных термов. У него два параметра: первый входной указывает имя домена, второй параметр конкретизируется термом домена, записанного в первом параметре. Если считанная этим предикатом строка не соответствует домену, указанному в его первом параметре, предикат выдаст сообщение об ошибке. Для записи данных в текущее устройство записи служит предикат write. Он может иметь произвольное количество параметров. Кроме того, в Турбо Прологе есть еще и предикат writef, который служит для форматного вывода данных. Для осуществления перехода на следующую строку (возврат каретки и перевод строки) применяется предикат nl, не имеющий параметров. Описанная ниже группа предикатов служит для преобразования типов. Предикат upper_lower имеет два аргумента и три варианта использования. Если в качестве первого аргумента указана строка (или символ), а второй аргумент свободен, то второй аргумент будет означен строкой (символом), полученной из первого аргумента преобразованием к нижнему регистру. Если в исходной строке были прописные английские буквы, то они будут заменены строчными. Если же, наоборот, первый аргумент свободен, а второй аргумент — это строка (или символ), то первый аргумент получит значение, равное строке (символу), полученной из второго аргумента преобразованием к верхнему регистру. Если в строке, находящейся во втором аргументе, были строчные английские буквы, то они будут заменены прописными. И, наконец, имеется третий вариант использования. Если и первый, и второй аргументы связаны, то предикат будет истинным только в том случае, если во втором аргументе находится строка (символ), которая получается из строки, находящейся в первом аргументе, путем замены всех прописных английских букв на строчные. В противном случае предикат будет ложным. Также имеют два параметра и три варианта использования предикаты str_int, str_real. Первый преобразует строку в целое число и наоборот. Второй служит для превращения строки в вещественное число или вещественного числа в строку. Предикат str_char имеет те же параметры использования и применяется для преобразования односимвольной строки в один символ и наоборот. Немного по-другому работает предикат char_int. Он позволяет переходить от символа к его ASCII-коду и обратно. Хотя Пролог — не самый лучший инструмент для выполнения большого объема вычислений, в нем имеются стандартные средства для реализации обычных вычислений. При этом можно использовать четыре арифметических операции (сложение (+), вычитание (-), умножение (*) и деление (/)), а также целочисленное деление (div) и взятие остатка от деления одного целого числа на другое (mod). Для сравнения чисел можно воспользоваться операциями равно (=), не равно (<>), больше (>), больше или равно (>=), меньше (

Не нашли то, что искали? Воспользуйтесь поиском:

Лучшие изречения: На стипендию можно купить что-нибудь, но не больше. 8983 — | 7233 — или читать все.

Условная компиляция в Delphi

Условная компиляция в Delphi, по сути, очень полезная и удобная возможность, которая позволяет Delphi компилировать или не компилировать код в зависимости от определенных символов условной компиляции. Говоря более простым языком (возможно и не совсем корректно), условная компиляция позволяет убрать или, наоборот, добавить часть кода исполняемого файла. Так или иначе, любой Delphi разработчик встречается с символами условной компиляции как минимум один раз за время работы над проектом.

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

Для начала, рассмотрим очень простой код, который вы можете повторить в Delphi за несколько секунд:

Теперь нажмите F9 и проверьте, что написано в отладчике в «Events»:

Разберемся с тем, что мы только что написали.

$IFDEF — это директива компилятора;

DEBUG — условное определение или символ условной компиляции. Символ обязательно должен начинаться с буквы за которой может следовать любое количество букв, цифр и знаков подчеркивания, однако использоваться будут лишь первые 255 символов.

Процедура отправляет строку в отладчик для отображения.

Завершает условную компиляцию, инициированную последней директивой <$IFxxx>(почему не <$IFDEF>— смотрим далее).

Таким образом, используя директивы и мы указали компилятору дословно следующее: если где-либо по ходу компиляции был встречено условное определение DEBUG, то надо выполнить OutputDebugString.

Где определено условное определение DEBUG? Конкретно в этом случае, символ DEBUG можно найти, если зайти в настройки проекта: Project -> Options ->Delphi Compiler :

Здесь же можно определить и свои собственные символы. Давайте, например, добавим свой символ условной компиляции TEST. Для этого открываем диалоговое окно редактирования символов условной компиляции (жмем кнопку «…» в строке «Conditional defines») и заносим наш символ в список:

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

Теперь можете снова запустить приложения в режиме отладки и посмотреть, что в Events появится строка «TEST IS ON».

Сейчас мы с вами рассмотрели самый простой способ использования условной компиляции в Delphi — использование директив и , а также определение собственного символа условной компиляции через свойства проекта. Вместе с этим, определить (включить) свой символ условной компиляции можно и в коде программы. Для этого используется директива компилятора . Воспользуемся этой директивой:

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

и убедиться, что символ DEBUG выключен, а в окне Events не появится строка «debug is on».

Двигаемся далее. Что делать, если нам необходимо вывести строку не когда символ включен, а именно тогда, когда он выключен? Здесь, опять же, есть варианты. Короткий вариант — воспользоваться директивой противоположной — она называется и код между и выполняется, если символ выключен:

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

Соответственно, нет необходимости далее повторять этот же участок кода с использованием — работать будет, но прямо противоположно.

Также следует обратить внимание на то, что все условные символы оцениваются в Delphi, когда вы выполняете Build проекта. Справка Delphi рекомендует для надежности пользоваться командой Project -> Build All Projects, чтобы быть уверенным, что все символы условной компиляции определены верно.

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

Например, символ условной компиляции VER330 определен для Delphi 10.3 Rio и с его помощью можно определить какой код должен или не должен выполняться, в случае, если версия компилятора Delphi — 33. Например, воспользуемся фичей Delphi 10.3 Rio под названием Inline Variable Declaration:

Сразу может возникнуть вопрос: как сделать так, чтобы приведенный выше код сработал не только в Delphi 10.3 Rio, но и в последующих версиях?
Это можно сделать воспользовавшись, например, такой конструкцией:

Здесь мы уже воспользовались директивой с помощью которой проверили значение константы CompilerVersion, которая находится в модуле System.

Здесь же стоит обратить внимание и на окончание блока — мы использовали директиву , как того требовала Delphi до версии Delphi XE4:

  • для директивы $IFDEF должна быть определена директива $ENDIF
  • для директивы $IF должна быть определена директива $IFEND

В XE4 нам разрешили использовать для закрытия блоков <$IF>, и . Однако, если у вас возникают проблемы при использовании связки и , то вы можете использовать специальную директиву , чтобы потребовать использовать для именно <$IFEND>:

Теперь, если в коде выше использовать директиву $ENDIF, то получим сообщение об ошибке:

Директиву , кстати, можно использовать и с другими константами проекта, например, так:

Так как наша константа Version содержит значение 2, то выполнится участок кода расположенный после . Можете сменить значение константы Version на 1, чтобы убедиться, что выполнится участок кода, где определена переменная s.

Как я сказал выше, в Delphi есть целый набор предопределенных символов условной компиляции. Ознакомиться со всеми этими символами вы можете на этой странице справки по Delphi.

  1. Использование условной компиляции позволяет нам выполнять тот или иной код, в зависимости от того, какие константы и символы условной компиляции определены или не определены в проекте.
  2. Используя предопредленные символы условной компиляции можно указывать Delphi какой код необходимо выполнить, например, если программа собирается под Android, или, если поддерживается архитектура x64 и т.д.
  3. Директива $IF может использоваться с различными константами, в том числе и определенными самим разработчиком.

При подготовке статьи использовалась следующая информация официальной справки по Delphi:

Выносим за скобки

Автор: Сергей Дуплик, Королевство Delphi

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

1. Выносим за условие

Здесь вычисляется значение некоторой функции, которое в зависимости от условия заносится то в один, то в другой массив. Напрашивается вопрос: а почему не вынести общий член за операторные скобки begin — end, точно так же, как в математике выносятся за скобки общие множители:

Исходный код стал более компактным, более понятным и меньшим по размеру. К тому же, вместо простого выражения y:=f(x) здесь может использоваться любое количество операторов. При изменении кода в первом случае нужно не забывать вносить изменения в оба места, что может привести к ошибкам.

2. Повторяющиеся участки кода — в процедуры

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

Итак, все повторение — за скобки.

3. Печатаем сообщение об ошибке

Пусть нам надо вывести сообщение об успешности выполнения некоторого действия. В случае успешного завершения выводится сообщение «Выполнение завершено успешно», в случае неуспешного — «Выполнение завершено неуспешно» или «Выполнение завершено с ошибкой».

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

Недостаток данного примера заключается в том, что в программе будут сохранены две почти идентичные строковые константы. Объявим строковую переменную s и перепишем его в следующем виде:

Для второго примера пишем:

Фактически данный пример является очередным выносом за скобки.

4. Присвоение значения логической переменной

Пусть есть две переменные a и b, значения которых нужно сравнить и логическая переменная IsAbove, которой нужно присвоить true, если a больше b и false в противном случае. Довольно часто это делается следующим образом:

Перепишем эту конструкцию проще, сэкономив некоторое количество памяти:

Скобки здесь использовать не обязательно, они нужны только для повышения читаемости кода.

5. Управление визуальными компонентами на форме

Одним из наиболее распространенных приемов является неэффективное управление логическими свойствами визуальных компонентов, такими как Enabled и Visible. К примеру (воспользуемся все той же переменной IsAbove):

Напрашивается следующее представление приведенного кода:

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

Перепишем его так:

Если компонентов, которым нужно установить not IsAbove, достаточно много, можно завести логическую переменную IsNotAbove, присвоить ей not IsAbove и использовать в дальнейшем.

Подведем итоги

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

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

Что каждый программист должен знать про оптимизации компилятора

Высокоуровневые языки программирования содержат в себе много абстрактных программистских конструкций, таких как функции, условные операторы и циклы — они делают нас удивительно продуктивными. Однако одним из недостатков написания кода на высокоуровневом языке является потенциальное значительное снижение скорости работы программы. Поэтому компиляторы стараются автоматически оптимизировать код и увеличить скорость работы. В наши дни логика оптимизации стала очень сложной: компиляторы преобразуют циклы, условные выражения и рекурсивные функции; удаляют целые блоки кода. Они оптимизируют код под процессорную архитектуру, чтобы сделать его действительно быстрым и компактным. И это очень здорово, ведь лучше фокусироваться на написании читабельного кода, чем заниматься ручными оптимизациями, которые будет сложно понимать и поддерживать. Кроме того, ручные оптимизации могут помешать компилятору выполнить дополнительные и более эффективные автоматические оптимизации. Вместо того чтобы писать оптимизации руками, лучше бы сосредоточиться на дизайне архитектуры и на эффективных алгоритмах, включая параллелизм и использование особенностей библиотек.

Данная статья посвящена оптимизациям компилятора Visual C++. Я собираюсь обсудить наиболее важные техники оптимизаций и решения, которые приходится применить компилятору, чтобы правильно их применить. Моя цель не в том, чтобы рассказать вам как вручную оптимизировать код, а в том, чтобы показать, почему стоит доверять компилятору оптимизировать ваш код самостоятельно. Эта статья ни в коем случае не является описанием полного набора оптимизаций, которые совершает компилятор Visual C++, в ней будут показаны только действительно важные из них, о которых полезно знать. Есть другие важные оптимизации, которые компилятор выполнить не в состоянии. Например, замена неэффективного алгоритма на эффективный или изменение выравнивания структуры данных. Такие оптимизации в этой статье мы обсуждать не будем.

Определение оптимизаций компилятора

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

Компиляторы постоянно совершенствуются, используемые ими подходы улучшаются. Несмотря на то, что они не совершенны, зачастую наиболее правильным подходом является всё-таки оставить низкоуровневые оптимизации компилятору, чем пытаться провести их вручную.

Существует четыре способа помочь компилятору провести оптимизации более эффективно:

  1. Пишите читаемый код, который легко поддерживать. Не думайте про разные ООП-фичи Visual C++ как о злейшем враге производительности. Последняя версия Visual C++ сможет свести накладные расходы от ООП к минимуму, а иногда даже полностью от них избавиться.
  2. Используйте директивы компилятора. Например, скажите компилятору использовать то соглашение о вызове функций, которое будет быстрее того, которое стоит по умолчанию.
  3. Используйте встроенные в компилятор функции. Это такие специальные функции, реализация которых обеспечивается компилятором автоматически. Помните, что компилятор обладает глубоким знанием того, как эффективно расположить последовательность машинных команд так, чтобы код работал максимально быстро на указанной программной архитектуре. В настоящее время Microsoft .NET Framework не поддерживает встроенные функции, так что управляемые языки не могут их использовать. Однако Visual C++ имеет расширенную поддержку таких функций. Впрочем, не стоит забывать о том, что их использование хоть и улучшит производительность кода, но при этом негативно скажется на читаемости и портируемости.
  4. Используйте profile-guided optimization (PGO). Благодаря этой технологии, компилятор знает больше о том, как код будет вести себя во время работы и оптимизирует его соответствующем образом.

Цель данной статьи состоит в том, чтобы показать вам, почему вы можете доверять компилятору выполнение оптимизаций, которые применяются к неэффективному, но читаемому коду (первый метод). Также я сделаю краткий обзор profile-guided оптимизаций и упомяну некоторые директивы компилятора, которые позволят вам улучшить часть вашего исходного кода.

Существует много техник оптимизаций компилятора, начиная от простых преобразований, вроде свёртки констант, заканчивая сложными, вроде управлением порядка команд (instruction scheduling). В этой статье мы ограничимся наиболее важными оптимизациями, которые могут значительно улучшить производительность вашего кода (на двухзначное количество процентов) и сократить его размер путём подстановки функций (function inlining), COMDAT оптимизаций и оптимизаций циклов. Я обсужу первые два подхода в следующем разделе, а затем покажу как можно контролировать выполнение оптимизаций в Visual C++. В заключение, я вкратце расскажу о тех оптимизациях, которые применяются в .NET Framework. На протяжении всей статьи я буду использовать Visual Studio 2013 для всех примеров.

Генерация кода на этапе линковки (Link-Time Code Generation, LTCG) — это техника для выполнения оптимизаций над всей программой (whole program optimizations, WPO) для С/С++ кода. Компилятор С/С++ обрабатывает каждый файл исходного кода по отдельности и выдаёт соответствующий файл объектов (object file). Другими словами, компилятор может оптимизировать только одиночный файл, вместо того, чтобы оптимизировать всю программу. Однако некоторые важные оптимизации могут быть применимы только к программе целиком. Вы можете использовать эти оптимизации только во время линковки, а не во время компиляции, т. к. линковщик имеет полное представление о программе.

Если LTCG включён (флаг /GL ), то драйвер компилятора ( cl.exe ) будет вызывать только front end ( c1.dll или c1xx.dll ) и отложит работу back end ( c2.dll ) до момента линковки. Полученные объектные файлы содержат C Inter­mediate Language (CIL), а не машинный код. Затем вызывается линковщик ( link.exe ). Он видит, что объектные файлы содержат CIL-код, и вызывает back end, который, в свою очередь, выполняет WPO и генерирует бинарные объектные файлы, чтобы линковщик мог соединить их вместе и сформировать исполняемый файл.

Front end также выполняет некоторые оптимизации (например, свёртка констант) независимо то того, включены или выключены оптимизации. Впрочем, все важные оптимизации выполняет back end, и их можно контролировать с помощью ключей компиляции.

LTCG позволяет back end-у выполнять многие оптимизации агрессивно (с помощью ключей компилятора /GL вместе с /O1 или /O2 и /Gw , а также с помощью ключей линковщика /OPT:REF и /OPT:ICF ). В данной статье я буду обсуждать только inlining и COMDAT оптимизации. Полный список LTCG-оптимизаций приведён в документации. Полезно знать, что линковщик может выполнить LTCG над нативными, нативно-управляемыми и чисто управляемыми объектными файлами, а также над безопасно-управляемыми (safe managed) объектными файлами и safe.netmodules.

Я буду работать с программой из двух файлов исходного кода ( source1.c и source2.c ) и заголовочным файлом ( source2.h ). Файлы source1.c и source2.c приведены в листинге ниже, а заголовочный файл, содержащий прототипы всех функций source2.c , настолько прост, что его приводить я не буду.

Файл source1.c содержит две функции: функцию square , вычисляющую квадрат целого числа, и главную функцию программы main . Главная функция вызывает функцию square и всех функции из source2.c за исключением isPrime . Файл source2.c содержит 5 функций: cube для возведения целого числа в третью степень, sum для подсчёта суммы целых чисел от 1 до заданного числа, sumOfCubes для подсчёта суммы кубов целых чисел от 1 до заданного числа, isPrime для проверки числа на простоту, getPrime для получения простого числа с заданным номером. Я пропустил обработку ошибок, т. к. она не представляет интереса в данной статье.

Код получился очень простой, но полезный. У нас есть несколько функций, которые делают простые вычисления, некоторые из них содержат циклы. Функция getPrime является самой сложной, т. к. она содержит цикл while , внутри которого вызывает функцию isPrime , которая также содержит цикл. Я буду использовать этот код для демонстрации одной из важных оптимизаций компилятора function inlining и нескольких дополнительных оптимизаций.

Рассмотрим результат работы компилятора под тремя разными конфигурациями. Если вы будете разбираться с примером самостоятельно, то вам понадобится assembler output file (получается с помощью ключа компилятора /FA[s] ) и map file (получается с помощью ключа линковщика /MAP ) чтобы изучить выполняемые COMDAT-оптимизации (линковщик будет сообщать о них, если вы включите ключи /verbose:icf и /verbose:ref ). Убедитесь, что все ключи указаны правильно, и продолжайте чтение статьи. Я буду использовать компилятор C ( /TC ), чтобы генерируемый код был проще для изучения, но всё излагаемое в статье также применимо к С++ коду.

Конфигурация Debug

Конфигурация Debug используется главным образом потому, что все back end оптимизации выключаются, если вы указываете ключ /Od без ключа /GL . В этой конфигурации итоговые объектные файлы содержит бинарный код, который в точности соответствует исходному коду. Вы можете изучить полученные assembler output files и map file, чтобы убедиться в этом. Конфигурация эквивалентна конфигурации Debug в Visual Studio.

Конфигурация Compile-Time Code Generation Release

Эта конфигурация похожа на конфигурацию Release (в которой указаны ключи /O1 , /O2 или /Ox ), но она не включает ключ /GL . В этой конфигурации итоговые объектные файлы содержат оптимизированный бинарный код, но при этом оптимизации уровня всей программы не выполняются.

Если вы посмотрите сгенерированный assembly listing file для source1.c , то заметите, что выполнились две важные оптимизации. Первый вызов функции square , square(n) , был заменён на вычисленное во время компиляции значение. Как такое произошло? Компилятор заметил, что тело функции мало, и решил подставить её содержимое вместо вызова. Затем компилятор обратил внимание на то, что в вычислении значения присутствует локальная переменная n с известным начальным значением, которое не менялось между первоначальным присваиванием и вызовом функции. Таким образом, он пришёл к выводу, что можно безопасно вычислить значение операции умножения и подставить результат ( 25 ). Второй вызов функции square , square(m) , также был заинлайнен, т. е. тело функции было подставлено вместо вызова. Но значение переменной m неизвестно на момент компиляции, так что компилятор не смог вычислить заранее значение выражения.

Теперь обратимся к assembly listing file для source2.c , он намного более интересный. Вызов функции cube в функции sumOfCubes был заинлайнен. Это, в свою очередь, позволило компилятору выполнить оптимизацию цикла (об этом подробнее в секции «Оптимизации циклов»). В функции isPrime были использованы инструкции SSE2 для конвертации int в double при вызове sqrt и конвертации из double в int при получении результата из sqrt . По факту, sqrt вызвалась единожды перед началом цикла. Обратите внимание, что ключ /arch указывает компилятору, что x86 использует SSE2 по умолчанию (большинство x86-процессоров и x86-64 процессоров поддерживают SSE2).

Эта конфигурация идентична конфигурации Release в Visiual Studio: оптимизации включены и ключ компилятора /GL указан (вы также можете явно указать /O1 или /O2 ). Тем самым мы говорим компилятору формировать объектные файлы с CIL кодом вместо assembly object files. А значит, линковщик вызовет back end компилятора для выполнения WPO, как было описано выше. Теперь мы обсудим несколько WPO, чтобы показать огромную пользу LTCG. Генерируемые assembly code-листинги для этой конфигурации доступны online.

Пока инлайнинг функций включён (ключ /Ob , который включён, если вы ключили оптимизации), ключ /GL позволяет компилятору инлайнить функции, определённые в других файлах независимо от ключа /Gy (мы обсудим его чуть позже). Ключ линковщика /LTCG опционален и влияет только на линковщик.

Если вы посмотрите на assembly listing file для source1.c , то можете заметить, что вызовы всех функций кроме scanf_s были заинлайнены. В результате компилятор смог выполнить вычисление функций cube , sum и sumOfCubes . Только функция isPrime не была заинлайнена. Однако если бы вы заинлайнили её вручную в getPrime , то компилятор по-прежнему выполнил бы инлайн getPrime в main .

Как вы можете видеть, инлайнинг функций — это важно не только из-за того, что оптимизируется вызов функций, но также из-за того, что он позволяет компилятору выполнить многие дополнительные оптимизации. Инлайнинг обычно увеличивает производительность за счёт увеличения размера кода. Чрезмерное использование этой оптимизации приводит к такому явлению, которое называется code bloat. Поэтому при каждом вызове функции компилятор проводит вычисление затрат и выгод, после чего принимает решение о том, стоит ли инлайнить функцию.

Из-за важности инлайнинга компилятор Visual C++ предоставляет большие возможности по его поддержке. Вы можете сказать компилятору, чтобы он никогда не инлайнил набор функций с помощью директивы auto_inline . Вы также можете указать компилятору заданные функции или методы с помощью __declspec(noinline) . А ещё можно пометить функцию ключевым словом inline и посоветовать компилятору выполнить инлайн (хотя компилятор может решить проигнорировать этот совет, если сочтёт его плохим). Ключевое слово inline доступно начиная с первой версии С++, оно появилось в С99. Вы можете использовать ключевое слово __inline компилятора от Microsoft и для С и для С++: это удобно, если вы хотите использовать старые версии С, которые не поддерживают данное ключевое слово. Ключевое слово __forceinline (для С и С++) заставляет компилятор всегда инлайнить функцию, если это возможно. И последнее, но не по важности, вы можете сказать компилятору развернуть рекурсивную функцию указанной или неопределённой глубины путём инлайнинга с помощью директивы inline_recursion . Учтите, что в настоящее время компилятор не имеет возможности контролировать инлайнинг в месте вызова функции, а не в месте её объявления.

Ключ /Ob0 отключает инлайнинг полностью, что полезно во время отладки (этот ключ срабатывает в Debug-конфигурации в Visual Studio). Ключ /Ob1 говорит компилятору, что в качестве кандидатов на инлайнинг должны рассматриваться только функции, помеченные с помощью inline , __inline , __forceinline . Ключ /Ob2 срабатывает только при указанных /O[1|2|x] и говорит компилятору рассматривать для инлайнинга все функции. На мой взгляд, единственная причина для использования ключевых слов inline и __inline — контролирования инлайнинга для ключа /Ob1 .

Компилятор не всегда может заинлайнить функцию. Например, во время виртуального вызова виртуальной функции: функция не может быть заинлайнена, т. к. компилятор не знает точно какая именно функция будет вызована. Другой пример: функция вызывается через указатель на функцию вместо вызова через её имя. Вы должны стараться избегать таких ситуаций, чтобы инлайнинг был возможен. Полный список всех подобных условий можно найти в MSDN.

Инлайнинг функций — это не единственная оптимизация, которая может применятся на уровне программы целиком. Большинство оптимизаций наиболее эффективно работают именно на этом уровне. В оставшейся части статьи я обсужу специфический класс оптимизаций, который называется COMDAT-оптимизации.

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

Для включения этих оптимизаций линковщика, вы должны попросить компилятор упаковывать функции и переменные в отдельные секции с помощью ключей компилятора /Gy (линковка уровня функций) и /Gw (оптимизация глобальных данных). Эти секции называются COMDAT-ами. Вы можете также пометить заданную глобальную переменную с использованием __declspec( selectany) , чтобы сказать компилятору упаковывать переменную в COMDAT. Далее, с помощью ключа линковщика /OPT:REF можно избавиться от неиспользуемых функций и глобальных переменных. Ключ /OPT:ICF поможет свернуть идентичные функции и глобальные константы (ICF — это Identical COMDAT Folding). Ключ /ORDER заставит линковщик помещать COMDAT-ы в итоговые образы в специфическом порядке. Учтите, что все оптимизации линковщика не нуждаются в ключе /GL . Ключи /OPT:REF и /OPT:ICF должны быть выключены во время отладки по очевидным причинам.

Вы должны использовать LTCG всегда, когда это возможно. Единственная причина отказа от LTCG заключается в том, что вы хотите распространять итоговые объектные файлы и файлы библиотек. Напомним, что они содержат CIL-код вместо машинного. CIL-код может быть использован только компилятором и линковщиком той же версии, с помощью которой они были сгенерированы, что является значительным ограничением, ведь разработчикам придётся использовать ту же версию компилятора, чтобы использовать ваши файлы. В данном случае, если вы не хотите распространять отдельную версию объектных файлов для каждой версии компилятора, то вы должны использовать вместо этого генерацию кода. В дополнение к ограничению по версии, объектные файлы во много раз больше, чем соответствующие assembler object-файлы. Впрочем, не забывайте про огромное преимущество объектных файлов с CIL-кодом, которое заключается в возможности использовать WPO.

Оптимизации циклов

Компилятор Visual C++ поддерживает несколько видов оптимизаций циклов, но мы будем обсуждать только три: размотка циклов (loop unrolling), автоматическая векторизация (automatic vectorization) и вынос инвариантов цикла (loop-invariant code motion). Если вы модифицируете код из source1.c так, что в sumOfCubes будет передаваться m вместо n, то компилятор не сможет высчитать значение параметров, придётся компилировать функцию, чтобы она могла работать для любого аргумента. Итоговая функция будет хорошо оптимизирована, ввиду чего будет иметь большой размер, а значит компилятор не будет её инлайнить.

Если вы скопмилируете код с ключом /O1 , то никакие оптимизации к sumOfCubes не применятся. Компиляция с ключом /O2 даст оптимизации по скорости. При этом размер кода значительно увеличится, т. к. цикл внутри функции sumOfCubes будет размотан и векторизован. Очень важно понимать, что векторизация будет невозможна без инлайнинга функции cube. Более того, размотка цикла также не будет столь эффективно без инлайнинга. Упрощённое графическое представление итогового кода приведено на следующей картинке (этот граф справедлив как для x86, так и для x86-64).

На данной схеме зелёный ромб показывает точку входа, а красные прямоугольники показывают точки выхода. Голубые ромбы представляют условные операторы, которые будут выполняться при выполнении функции sumOfCubes . Если SSE4 поддерживается и x больше или равно 8, то SSE4-инструкции будут использованы для того, чтобы выполнять 4 умножения за 1 раз. Процесс выполнения одинаковой операции для нескольких переменных называется векторизацией. Также компилятор дважды размотает этот цикл. Это означает, что тело цикла будет повторено дважды на каждую итерацию. В результате выполнение восьми операций умножения будет происходить за 1 итерацию. Если x меньше 8, то для выполнения функции будет использован код без оптимизаций. Обратите внимание, что компилятор вставляет три точки выхода вместо одной — таким образом уменьшается количество переходов.

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

В отличие от x86-процессоров, все процессоры x86-64 поддерживают SSE2. Более того, вы можете использовать преимущества инструкций AVX/AVX2 на последних моделях x86-64 процессоров от Intel и AMD с помощью ключа /arch . Указав /arch:AVX2 , вы скажете компилятору также использовать инструкции FMA и BMI.

На текущий момент компилятор Visual C++ не позволяет контролировать размотку циклов. Но вы можете влиять на неё с помощью __forceinline и директивы loop c опцией no_vector (последняя выключает автовекторизацию заданных циклов).

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

Функция someOfCubes не является единственной, цикл которой был размотан. Если вы модифицируете код и передадите m в функцию sum вместо n , то компилятор не сможет предподсчитать её значение и ему придётся генерировать код, цикл будет размотан дважды.

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

Единственное изменение, которое мы сделали, заключается в добавлении дополнительной переменной, которая увеличивается на каждой итерации, а в завершении выводится на консоль. Нетрудно заметить, что данный код легко оптимизируется с помощью выноса инкрементируемоей переменной за рамки цикла: достаточно просто присвоить ей значение x . Данная оптимизация называется выносом инварианта цикла (loop-invariant code motion). Слово «инвариант» показывает, что данная техника применима тогда, когда часть кода не зависит от выражений, включающих переменную цикла.

Но вот загвоздка: если вы примените эту оптимизацию вручную, то итоговый код может потерять в производительности в определённых условиях. Можете ли вы сказать почему? Представьте, что переменная x не положительна. В этом случае цикл не будет выполняться, а в неоптимизированной версии переменная count останется нетронутой. Версия соптимизированная вручную выполнит лишнее присваивание из x в count, которое будет выполнено вне цикла! Более того, если x отрицательно, то переменная count получит неверное значение. И люди, и компиляторы подвержены подобным ловушкам. К счастью, компилятор Visual C++ достаточно умён, чтобы догадаться до этого и проверить условие цикла до присваивания, улучшая производительность для всех возможных значений x .

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

Контролирование оптимизаций

В дополнение к ключам O1 , /O2 , /Ox , вы можете контролировать специфические функции с помощью директивы optimize :

В данном примере optimization list может быть пустым, а может содержать одно или несколько значений из набора: g , s , t , y . Они соответствуют ключам /Og , /Os , /Ot , /Oy .

Пустой список c параметром off выключает все оптимизации вне зависимости от ключей компилятора. Пустой список с параметром on применяет вышеозначенные ключи компилятора.

Ключ /Og позволяет выполнять глобальные оптимизации, которые могут быть выполнены только внутри оптимизируемой функции, а не в функциях, которые её вызывают. Если LTCG включён, то /Og позволяет делать WPO.

Директива optimize очень полезна в случаях, когда вы хотите, чтобы разные функции были оптимизированы разными способами: одни по занимаемому размеру, а другие по скорости. Впрочем, если вы действительно хотите иметь контроль оптимизаций такого уровня, вы должны посмотреть в сторону profile-guided-оптимизаций (PGO), которые представляют собой оптимизацию кода с использованием профиля, хранящего информацию о поведении, записанную во время выполнения инструментальной версии кода. Компилятор использует профиль для того, чтобы обеспечить лучшие решения во время оптимизации кода. Visual Studio представляет специальные инструменты, чтобы применить эту технику как к нативному, так и к управляемому коду.

Оптимизации в .NET

В .NET нет линковщика, который был бы вовлечён в модель компиляции. Вместо этого есть компилятор исходного кода (C# compiler) и JIT-компилятор. Над исходным кодом выполняются только минорные оптимизации. Например, на этом уровне вы не увидите инлайнинга функции или оптимизаций циклов. Вместо этого данные оптимизации выполняются на уровне JIT-компиляции. JIT-компилятор до версии .NET 4.5 не поддерживал SIMD. А вот JIT-компилятор с версии .NET 4.5.1 (который называется RyuJIT) поддерживает SIMD.

В чём же разница между RyuJIT и Visual C++ с точки зрения возможностей оптимизации? Ввиду того, что RyuJIT работает во время выполнения, он может выполнить такие оптимизации, на которые Visual C++ не способен. Например, прямо во время выполнения он может понять, что выражение в условном операторе никогда не примет значение true в текущей запущенной версии приложения, а затем применить соответствующие оптимизации. Также RyuJIT может использовать знания об используемой процессорной архитектуре во время выполнения. Например, если процессор поддерживает SSE4.1, то JIT-компилятор использует только инструкции SSE4.1 для реализации функции subOfCubes , что позволит сделать генерируемый код более компактным. Но нужно понимать, что RyuJIT не может тратить на оптимизации много времени, т. к. время JIT-компиляции влияет на производительность приложения. А вот компилятор Visual C++ может потратить на анализ кода много времени, чтобы найти возможности оптимизации для улучшения итогового исполняемого файла. Благодаря новой замечательной технологии от Microsoft под названием .NET Native вы можете компилировать управляемый код в самодостаточные исполняемые файлы с использованием оптимизаций Visual C++. В настоящее время эта технология поддерживает только приложения из Windows Store.

$Optimization — Директива компилятора Delphi

все что посвящено электронике и общению специалистов. реклама других ресурсов.

В помощь начинающему

вопросы начального уровня

International Forum

This is a special forum for English spoken people, read it first.

Образование в области электроники

все что касается образования, процесса обучения, студентам, преподавателям.

Обучающие видео-материалы и обмен опытом

Обсуждение вопросов создания видео-материалов

Cистемный уровень проектирования

    Последнее сообщение

Вопросы системного уровня проектирования

Применение MATLAB, Simulink, CoCentric, SPW, SystemC ESL, SoC

Математика и Физика

Операционные системы

Linux, Win, DOS, QNX, uCOS, eCOS, RTEMS и другие

Документация

оформление документации и все что с ней связано

Разработка цифровых, аналоговых, аналого-цифровых ИС

Электробезопасность и ЭМС

Обсуждение вопросов электробезопасности и целостности сигналов

Управление проектами

Управление жизненным циклом проектов, системы контроля версий и т.п.

Нейронные сети и машинное обучение (NN/ML)

Форум для обсуждения вопросов машинного обучения и нейронных сетей

Программируемая логика ПЛИС (FPGA,CPLD, PLD)

    Последнее сообщение

Среды разработки — обсуждаем САПРы

Quartus, MAX, Foundation, ISE, DXP, ActiveHDL и прочие.
возможности, удобства.

Работаем с ПЛИС, области применения, выбор

на чем сделать? почему не работает? кто подскажет?

Языки проектирования на ПЛИС (FPGA)

Verilog, VHDL, AHDL, SystemC, SystemVerilog и др.

Системы на ПЛИС — System on a Programmable Chip (SoPC)

разработка встраиваемых процессоров и периферии для ПЛИС

Цифровая обработка сигналов — ЦОС (DSP)

    Последнее сообщение

Сигнальные процессоры и их программирование — DSP

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

Алгоритмы ЦОС (DSP)

Обсуждение вопросов разработки и применения (программирования) алгоритмов цифровой обработки сигналов.

Микроконтроллеры (MCs)

    Последнее сообщение

Cредства разработки для МК

FAQ, How-to, тонкости работы со средствами разработки

MSP430

Все остальные микроконтроллеры

и все что с ними связано

Отладочные платы

Вопросы, связанные с отладочными платами на базе МК: заказ, сборка, запуск

Печатные платы (PCB)

    Последнее сообщение

Разрабатываем ПП в САПР — PCB development

FAQ, вопросы проектирования в ORCAD, PCAD, Protel, Allegro, Spectra, DXP, SDD, WG и др.

Работаем с трассировкой

тонкости PCB дизайна, от Spectra и далее.

Изготовление ПП — PCB manufacturing

Фирмы, занимающиеся изготовлением, качество, цены, сроки

Сборка РЭУ

    Последнее сообщение

Пайка, монтаж, отладка, ремонт

вопросы сборки ПП, их отладки, различного рода ремонт

Корпуса

обсуждаем какие есть копруса, где делать и прочее

Вопросы надежности и испытаний

расчеты, методики, подбор компонентов

Аналоговая и цифровая техника, прикладная электроника

    Последнее сообщение

Вопросы аналоговой техники

пока помещаются в одном форуме

Цифровые схемы, высокоскоростные ЦС

High Speed Digital Design

Rf & Microwave Design

wireless технологии и не только

Метрология, датчики, измерительная техника

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

АВТО электроника

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

Умный дом

3D печать

3D принтеры, наборы, аксессуары, ПО

Робототехника

Модели, классификация, решения, научные исследования, варианты применения

Силовая Электроника — Power Electronics

    Последнее сообщение

Силовая Преобразовательная Техника

Источники питания электронной аппаратуры, импульсные и линейные регуляторы. Топологии AC-DC, DC-DC преобразователей (Forward, Flyback, Buck, Boost, Push-Pull, SEPIC, Cuk, Full-Bridge, Half-Bridge). Драйвера ключевых элементов, динамика, алгоритмы управления, защита. Синхронное выпрямление, коррекция коэффициента мощности (PFC)

Обратная Связь, Стабилизация, Регулирование, Компенсация

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

Первичные и Вторичные Химические Источники Питания

Li-ion, Li-pol, литиевые, Ni-MH, Ni-Cd, свинцово-кислотные аккумуляторы. Солевые, щелочные (алкалиновые), литиевые первичные элементы. Применение, зарядные устройства, методы и алгоритмы заряда, условия эксплуатации. Системы бесперебойного и резервного питания

Высоковольтные Устройства — High-Voltage

Высоковольтные выпрямители, умножители напряжения, делители напряжения, высоковольтная развязка, изоляция, электрическая прочность. Высоковольтная наносекундная импульсная техника

Электрические машины, Электропривод и Управление

Электропривод постоянного тока, асинхронный электропривод, шаговый электропривод, сервопривод. Синхронные, асинхронные, вентильные электродвигатели, генераторы

Индукционный Нагрев — Induction Heating

Технологии, теория и практика индукционного нагрева

Системы Охлаждения, Тепловой Расчет – Cooling Systems

Охлаждение компонентов, систем, корпусов, расчёт параметров охладителей

Моделирование и Анализ Силовых Устройств – Power Supply Simulation

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

Компоненты Силовой Электроники — Parts for Power Supply Design

Силовые полупроводниковые приборы (MOSFET, BJT, IGBT, SCR, GTO, диоды). Силовые трансформаторы, дроссели, фильтры (проектирование, экранирование, изготовление), конденсаторы, разъемы, электромеханические изделия, датчики, микросхемы для ИП. Электротехнические и изоляционные материалы.

Язык Delphi для Win32 и. Net — синтаксис, операции, операторы

Название Язык Delphi для Win32 и. Net — синтаксис, операции, операторы
страница 3/11
Дата публикации 04.06.2013
Размер 1.12 Mb.
Тип Документы

litcey.ru > Информатика > Документы

<$0±>, <$OPTIMIZATION>— директивы оптимизации

^ Директивы компилятора, управляющие оптимизацией

Область действия локальная

Директива <$0+>обеспечивает компиляцию с оптимизацией: размещение переменных в быстрых регистрах и многое другое. Оптимизация повышает эффективность программы, иногда сокращает выполняемый файл, но может затруднять отладку из-за устранения из кода некоторых операторов и переменных. Так что во время отладки оптимизацию полезно отключать, но при компиляции окончательного варианта программы оптимизацию обязательно надо включать.

Директивы $0 действуют только целиком на функцию или процедуру. Их нельзя включить или выключить для какой-то части процедуры.

Директивы компилятора, управляющие экспортом символов

Область действия глобальная

Директива <$ Ob j Export All ON>обеспечивает экспорт всех символов, содержащихся в модуле. Эта директива введена для связи модулей Delphi с C++Builder и позволяет в C++Builder создавать пакеты, содержащие объектные файлы, написанные на Delphi.

<$OPENSTRINGS>— директивы управления строковыми параметрами

См. «<$Р>, <$OPENSTRINGS>— директивы управления строковыми пара- параметрами».

<$OVERFLOWCHECKS>— директивы проверки переполнения при целочисленных операциях

См. «<$Q>, <$OVERFLOWCHECKS>— директивы проверки переполнения при целочисленных операциях».

<$Р±>, <$OPENSTRINGS>—директивы управления строковыми параметрами

Директивы компилятора, управляющие способом представления строковых параметров

Область действия локальная

Директивы $Р используются только при включенной директиве <$Н->для обратной совместимости с ранними версиями Delphi и Borland Pascal. Директивы управляют способом представления переменных параметров, объявленных как string. При директиве <$Р->эти параметры являются обычными, а при директиве <$Р+>они являются параметрами в виде открытых строк. Такие открытые строки ранее использовались для передачи в качестве параметров строк неуказанной длины.

Независимо от значения опции $Р параметр всегда может быть определен как открытая строка явным указанием типа параметра OpenString.

<$Q±>, <$OVERFLOWCHECKS>—директивы проверки переполнения при целочисленных операциях

Директивы компилятора, включающие и выключающие проверку переполнения при целочисленных операциях

Область действия локальная

Директивы включают или выключают проверку переполнения при целочисленных операциях. Под переполнением понимается получение результата, который не может сохраняться в регистре компьютера. При включенной директиве <$Q+>проверяется переполнение при целочисленных операциях +, —, *, Abs, Sqr, Succ, Pred, Inc и Dec. После каждой из этих операций размещается код, осуществляющий соответствующую проверку. Если обнаружено переполнение, то генерируется исключение EIntOverflow в приложениях VCL Win32 или исключение OverflowException в приложениях .NET. Если в программе не предусмотрена обработка этого исключения, выполнение программы завершается.

Переполнение определяется регистрами компьютера и не совпадает в системах. Вычисления, вызывающие переполнение в 16-битных приложениях, могут в ряде случаев нормально осуществиться в 32-битном приложении. Вместо них в 32-битной программе может генерироваться исключение ошибки диапазона ERangeError. Если переполнение случилось до операции присваивания, то генерируется исключение EIntOverflow в приложениях VCL Win32 или исключение OverflowException в приложениях .NET. А если переполнение произошло в момент присваивания, то генерируется исключение ERangeError.

Директивы $Q проверяют только результат арифметических операций. Обычно они используются совместно с директивами <$R+>, <$R->, проверяющими диапазон значений при присваивании.

Директива <$Q+>замедляет выполнение программы и увеличивает ее размер. Поэтому обычно она используется только во время отладки программы. Однако надо отдавать себе отчет, что отключение этой директивы приведет к появлению ошибочных результатов расчета в случаях, если переполнение действительно произойдет во время выполнении программы (см. разд. 2.8.3). Причем сообщений о подобных ошибках не будет.

Директивы компилятора, включающие и выключающие проверку диапазона целочисленных значений и индексов

Область действия локальная

Директивы включают или выключают проверку диапазона целочисленных значений и индексов. Если включена директива <$R+>, то все индексы массивов и строк и все присваивания скалярным переменным и переменным с ограниченным диапазоном значений проверяются на соответствие значения допустимому диапазону. Если требования диапазона нарушены или присваиваемое значение слишком велико, генерируется исключение ERangeError. Если оно не перехвачено, выполнение программы завершается.

Проверка диапазона строк типа LongStrings не производится.

Директива < $R+>замедляет работу приложения и увеличивает его размер. Поэтому она обычно используется только во время отладки. Директивы <$R+>и <$R->имеет смысл использовать только в приложениях VCL Win32. В приложениях .NET проверка допустимого диапазона проводится независимо от этих директив и при ошибках выполнения выдается, например, следующее сообщение: «Индекс находился вне границ массива». Это очень удобно, так как позволяет легко выявлять ошибки, которые иным способов отлавливаются с большим трудом.

<$REALCOMPATIBILITY>— директивы, управляющие интерпретацией типа Real

Директивы компилятора, управляющие интерпретацией типа Real:

Область действия локальная

При включенной по умолчанию директиве <$REALCOMPATIBILITY OFF>тип Real интерпретируется как Double, а при включенной директиве <$REALCOMPATIBILITY ON>тип Real интерпретируется как Real48. Директивы поддерживаются для обратной совместимости с кодами, в которых Real трактовался как 6-байтовое целое, называемое теперь Real48. Для большинства приложений более предпочтительной интерпретацией Real является Double.

<$REFERENCEINFO>— директивы генерации ссылок на символы

<$SAFEDIVIDE>— директивы устранения ошибок ранних версий процессоров Pentium

См. «<$U>, <$SAFEDIVIDE>— директивы устранения ошибок ранних версий процессоров Pentium».

<$STACKFRAMES>— директивы управления генерацией стека

См. «<$W>, <$STACKFRAMES>— директивы управления генерацией стека».

<$Т±>, <$TYPEDADDRESS>— директивы, управляющие типами указателей

Директивы компилятора, управляющие типами указателей, генерируемых операцией@

По умолчанию <$Т->или

^ Область действия глобальная

Директивы $Т управляют типами указателей, генерируемых операцией @, и совместимостью типов указателей.

При директиве <$Т->результатом операции @ всегда является не типизированный указатель Pointer, совместимый с любыми другими типами указателей. Различные указатели, отличные от Pointer, несовместимы друг с другом, даже если указывают на переменные одного типа.

При директиве <$Т+>результатом применения операции @ к некоторой переменной является типизированный указатель, совместимый только с типом Pointer и с другими указателями на переменные того же типа. Различные указатели, указывающие на переменные одного типа, совместимы друг с другом.

<$TYPEINFO>— директивы информации времени выполнения о типах

См. «<$М>, <$TYPEINFO>— директивы информации времени выполнения о типах».

< $U±>, < $ S AFEDIVIDE>— директивы устранения ошибок ранних версий процессоров Pentium

Директивы компилятора, включающие и выключающие устранения ошибок ранних версий процессоров Pentium

Область действия локальная

Директивы включают или выключают генерацию кодов выполнения операций с плавающей запятой, устраняющих ошибки первых версий процессоров Pentium. В системах Windows 95, Windows NT 3.51 и более поздних эти ошибки устраняются самими системами.

<$UNSAFECODE>— директивы управления использованием ключевого слова unsafe

Директивы компилятора, включающие и выключающие возможность использования в коде ключевого слова unsafe

Область действия локальная

Директива <$UNSAFECODE ON>разрешает, а директива <$UNSAFECODE OFF>запрещает использовать в заголовках функций и процедур ключевое слово unsafe.

Этими директивами также надо окружать объявления глобальных переменных типа указателей. Например:

Директивы имеют смысл только в приложениях VCL .NET и в Windows Forms. Пример применения директив при объявлении небезопасных функций и более подробные пояснения см. в разд. 2.7.6.

Директивы проверки коротких строк, передаваемых как параметры

Область действия локальная

Директивы $V поддерживаются для обратной совместимости с ранними версиями Delphi и Borland Pascal. Они управляют проверкой длины коротких строк, передаваемых в функции и процедуры в качестве параметров. Директива <$V+>обеспечивает проверку, одинаковая ли длина формального и реального параметров. При директиве <$V->допускается отличие длины объявленного и формального параметра.

<$W>, <$STACKFRAMES>—директивы управления генерацией стека

Директивы компилятора, управляющие генерацией стека функций и процедур

Область действия локальная

Директива < $ W+>обеспечивает генерацию стека для функций и процедур, даже если в этом нет необходимости. При директиве <$W->стек генерируется только в случаях, когда процедура или функция имеет локальные переменные. Включение директивы <$W+>имеет смысл только для функционирования некоторых инструментов отладчика Delphi.

<$WARN>— директивы предупреждений компилятора

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

Область действия локальная

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

В приведенной таблице речь идет о специальных модификаторах (директивах) рекомендаций, указаний (hint), введенных в Delphi. Это модификаторы platform, deprecated и library. Они могут применяться в объявлениях типов, переменных, классов, записей, полей классов и записей, в объявлениях процедур, функций, методов или в объявлении модуля. Обычно модификатором platform помечаются элементы, которые специфичны для той или иной операционной системы, например, Windows или Linux. Модификатором library помечаются элементы, которые специфичны для используемой библиотеки компонентов. А модификатором deprecated помечаются элементы, устаревшие или оставленные только для обратной совместимости с более ранними версиями. Можно также использовать в коде модификатор experimental, помечая им, например, временные, экспериментальные процедуры, которые вы в дальнейшем хотите усовершенствовать.

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

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

unit Unitl library;

function F(a:double): double; deprecated;

var VN: Real library;

type MyError = class(Exception)

Сообщения компилятора, установленные директивой <$WARN identifier ON>, появляются при компиляции каждого оператора, использующего элементы (переменные, функции и т.п.), помеченные рассмотренными модификаторами. См. также директивы <$ WARNINGS>, которые тоже управляют появлением замечаний компилятора.

<$WARNINGS>— директивы управления предупреждениями компилятора

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

Область действия локальная

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

Тогда по отношению к этому фрагменту не будут делаться указанные предупреждения.

Илон Маск рекомендует:  Линии и рамки
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL