Глава 4. Препроцессор и особенности компилятора

Чтобы закончить наш, так сказать, пропедевтический курс по языку С, нам нужно еще рассказать о препроцессоре; и о некоторых ключевых словах C++Builder, которые не укладываются в стандарт ANSI. Кроме того, мы расскажем о диалоге Project | Options, в котором задаются различные параметры, управляющие компиляцией программы.

Директивы препроцессора

Препроцессорная обработка представляет собой первую фазу того процесса, что именуется компиляцией программы на C/C++. Компилятор C++Builder не генерирует промежуточного файла после препроцессорной обработки. Однако, если хотите, можно посмотреть на результат работы препроцессора, запустив отдельную программу срр32.ехе из командной строки:

срр32 myfile.c

Макроопределения

Макроопределения, называемые в просторечии макросами, определяются директивой препроцессора #define. Можно выделить три формы макросов #define: простое определение символа, определение символической константы и определение макроса с параметрами.

Простое определение выглядит так:

#define NDEBUG

После такой директивы символ NDEBUG считается определенным. Не предполагается, что он что-то означает; он просто — определен (как пустой). Можно было бы написать:

#define NDEBUG 1

Тогда NDEBUG можно было бы использовать и в качестве символической константы, о которых говорилось в предыдущей главе. Всякое вхождение в текст лексемы NDEBUG препроцессор заменил бы на “I”. Зачем нужны макроопределения, которые ничего не определяют, выяснится при обсуждении условных конструкций препроцессора.

Как вы могли бы догадаться, #define может определять не только константы. Поскольку препроцессор выполняет просто текстовую подстановку, можно сопоставить символу и любую последовательность операторов, как показано ниже:

#define SHUTDOWN \

printf("Error!"); \ return -1

if (ErrorCondition()) SHUTDOWN; // "Вызов" макроса.

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

Определенный ранее макрос можно аннулировать директивой #undef:

#undef NDEBUG

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

Предопределенные макросы

Компилятор C++Builder автоматически определяет некоторые макросы. Их можно разбить на две категории: макросы ANSI и макросы, специфические для C++Builder. Сводки предопределенных макросов даны соответственно в таблицах 4.1 и 4.2.

Таблица 4.1. Предопределенные макросы ANSI

Макрос

Описание

DATE

Литеральная строка в формате “mmm dd yyyy”, представляющая дату обработки данного файла препроцессором.

FILE

Строка имени текущего файла (в кавычках).

LIME

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

STDC

Равно 1, если установлена совместимость компилятора со стандартом ANSI (ключ -А командной строки). В противном случае макрос не определен.

TIME

Строка в формате “hh:mm:ss”, представляющее время препроцессорной обработки файла.

 

Значения макросов _file_ и _line_ могут быть изменены директивой #line (см. далее).

 

Таблица 4.2. Предопределенные макросы C++Builder

Макрос

Значение

Описание

ВСОРТ

1

Определен в любом оптимизирующем компиляторе.

BCPLUSPLUS

0х0540

Определен, если компиляция производится в режиме C++. В последующих версиях будет увеличиваться.

BORLANDC

0х0540

Номер версии.

CDECL

1

Определен, если установлено соглашение о вызове cdecl; в противном случае не определен.

CHARUNSIGNED

1

Определен по умолчанию (показывает, что char по умолчанию есть unsigned char). Можно аннулировать ключом -К.

CONSOLE

 

 

Определен при компиляции консольных приложений.

CPPUNWIND

1

Разрешение разматывания стека; определен по умолчанию. Для аннулирования можно применить ключ -xd-.

cplusplus

1

Определен при компиляции в режиме C++.

DLL

1

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

FLAT

1

Определен при компиляции в 32-битной модели памяти.

MIХ86

 

 

Определен всегда. Значение по умолчанию — 300. (Можно изменить значение на 400 или 500, применив соответственно ключи /4 или /5 в командной строке.)

MSDOS

1

Целая константа.

MT

1

Определен, если установлена опция -WM. Она означает, что будет присоединяться мультили-нейная (multithread) библиотека.

PASCAL

1

Определен, если установлено соглашение о вызове Pascal.

TCPLUSPLUS

0х0540

Определен, если компиляция производится в режиме C++ (аналогично bcplusplus ).

TEMPLATES

1

Определен для файлов C++ (показывает, что поддерживаются шаблоны).

TLS

1

Thread Local Storage. В C++Builder определен всегда.

TURBOC

0х0540

Номер версии (аналогичен BORLANDC ).

WCHAR T

1

Определен только в программах C++ (показывает, что wear t — внутренне определенный тип.

WCAR T DEFINED

1

То же, что и WCHAR Т.

Windows

 

 

Определен для кода, используемого только в Windows.

WIN32

1

Определен для консольных и GUI-приложений.

 

Как видите, многие предопределенные макросы C++Builder отражают те или иные установки параметров компиляции, задаваемые в командной строке (при ручном запуске компилятора Ьсс32.ехе). Те же самые установки могут быть выполнены и в интегрированной среде через диалог Project Options, который мы еще будем рассматривать в этой главе.

Макросы с параметрами

Макросы могут выполнять не только простую текстовую подстановку. Возможно определение макросов с параметрами, напоминающих функции языка С, например:

#define PI 3.14159265

#define SQR(x) ( (x) * (x) )

#define AREA(x) (PI * SQR(x))

#define MAX(a, b) (<a)>(b) ? (a): (b))

circleArea = AREAfrl + r2);

cMax = MAX(i++, j++);

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

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

#define SQR(x) х*х binom = -SQR(a + b) ;

При расширении макроса получится:

binom = -a + b*a + b;

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

Преобразование в строку

В макросах может применяться специальная операция преобразования в строку (#). Если в расширении макроса параметру предшествует эта опе-

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

#define SHOWINT(var)

printf(#var " = %d\n", (int)(var))

int iVariable = 100;

SHOWINT(iVariable) ;

Последняя строчка расширяется в

printf("iVariable"" = %d\n", (int)(iVariable));

и печатает

iVariable = 100

В С примыкающие друг к другу литеральные строки при компиляции соединяются в одну строку.

 

Конкатенация

Операция конкатенации (##) позволяет составить из нескольких лексем единое слово. Получившийся элемент повторно сканируется для обнаружения возможного идентификатора макроса. Рассмотрите такой код:

#define DEF_INT(n) int iVar ## n

DEF_INT(One); // Расширяется в int iVarOne;

DEF_INT(Two); // Расширяется в int iVarTwo; и т.д.

Директива # include

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

#include <filename> #include "filename"

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

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

Можно производить выборочную компиляцию различных участков кода в зависимости от оценки некоторого константного выражения или определения идентификатора. Для этого служат директивы #if, #elif, #else и #endif. Общая форма применения директив условной компиляции следующая:

# выражение_1

группа_операторов 1

[# elif выражение_2

группа_опера торов_2

# elif выражение_3

группа_ операторов_ 3...]

[# else группа операторов else]

#endif

Первая группа операторов компилируется, если выражение_1 истинно; в противном случае операторы ее опускаются. Вторая группа компилируется, если выражение_1 ложно и выражение_2 истинно и т. д. Группа #else компилируется только в том случае, если все условные выражения ложны. Конструкция условной компиляции должна заканчиваться директивой #endif.

Разделы #elifH#else могут отсутствовать. Необходимыми элементами условной конструкции являются только директивы #if и #endif.

Операции в условиях #if

Выражения в директивах могут содержать обычные операции отношения <, >, <=, >= и ==. С их помощью можно проверять, например, значения предопределенных макросов или идентификаторов, определяемых директивой #define. В директивах препроцессора имеется также одна специальная операция defined. Она позволяет проверить, определен ли некоторый символ, например:

#define TEST

#if defined(TEST)

testFile = open("TEST.$$$", 0_CREAT | 0_TEXT) ;

#else testFile = -1;

#endif

Операция defined может комбинироваться с логическим отрицанием (!). ! defined (Sym) будет истинным, если Sym не определен.

Директивы #ifdef и ftifndef

Эти две директивы эквивалентны соответственно #if defined и #if !defined.

Директива #error

С помощью этой директивы можно вывести сообщение об ошибке при компиляции.

#error сообщение

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

#ifndef WIN_VERSION

terror He определена версия Windows!

#endif

Директива # linе

Директива позволяет установить внутренний счетчик строк компилятора, возвращаемый макросом _LINE_. Она имеет следующий вид:

#line номер строки ["имя файла"]

Номер_строки должен быть целой константой. Если указано необязательное имя_файла, то модифицируется также значение макроса_FILE_.

Директива # pragma

Эта директива служит для установки параметров, специфичных для компилятора. Часто выполняемые с ее помощью установки можно выполнить и другим способом, например, в диалоге Project Options или в командной строке компилятора. Директива имеет вид

#pragma директива

Что такое директива, описывает следующая таблица.

Таблица 4.3. Директивы “pragma компилятора C++Builder

Директива

Описание

alignment

Выдает сообщение о текущем выравнивании данных и размере enum-типов.

anon struct

Синтаксис:

#pragma anon struct on ^pragma anon struct off

Разрешает или запрещает использование вложенных анонимных структур в классах.

argsused

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

checkoption

Синтаксис:

#pragma checkoption строкаопций

Проверяет, установлены ли опции, указанные в директиве. Если нет, выдается сообщение об ошибке.

codeseg

Синтаксис:

#pragma codeseg [имясегмента] ["класс"] [группа]

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

comment

Записывает строку-комментарий в объектный или исполняемый файл. Синтаксис:

ftpragma comment (тип, "строка")

exit

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

#pragma exit имя функции [приоритет]

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

hdrfile

Специфицирует имя файла прекомпилируемых заголовков.

hdrstop

Запрещает включать дальнейшую информацию в файл прекомпилируемых заголовков.

inline

Говорит компилятору, что файл должен компилироваться через ассемблер (компилятор генерирует код ассемблера, затем запускает TASM, который выдает конечный obj-файл).

intrinsic

Синтаксис:

#pragma intrinsic [-]имяфункции

Управляет inline-расширением внутренних (intrinsic) функций (к ним относятся в основном функции исполнительной библиотеки для работы со строками, такие, как strncpy, memset и другие).

link

Синтаксис:

#pragma link "имяфайла"

Заставляет компоновщик подключить к исполняемому модулю указанный объектный файл.

message

Синтаксис:

#pragma message ("текст"...) ttpragma message текст

Выдает сообщение при компиляции.

nopushoptwarn

Подавляет предупреждение о том, что опции компилятора, имевшиеся в начале обработки текущего "файла, не были восстановлены к концу его компиляции (см. ниже о #pragma option).

obsolete

Синтаксис:

#pragma obsolete имяфункции

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

option

Синтаксис:

#pragma option опции #pragma option push опции #pragma option pop

Директива позволяет указать необходимые опции командной строки прямо в коде программы. Форма option push сначала сохраняет текущие установки в стеке компилятора; option pop, соответственно, выталкивает из стека последний набор опций.

pack

Синтаксис:

#pragma pack(n)

#pragma pack (push, n)

#pragma pack(pop)

Задает выравнивание данных в памяти и эквивалентна

ftpragma option -an.

package

Синтаксис:

#pragma package(smart init)

#pragma package(smart init, weak)

Управляет порядком инициализации модулей в пакетах C++Builder; по умолчанию включается в начало каждого автоматически создаваемого модуля.

resource

Синтаксис:

tpragma resource "*.dfm"

Текущий файл помечается как модуль формы; в текущем каталоге должны присутствовать соответствующий dfrn-файл и заголовок. Всеми этими файлами IDE управляет автоматически.

startup

Аналогична pragma exit; позволяет специфицировать функции, исполняющиеся при запуске программы (перед вызовом main). Функции с более высоким приоритетом вызываются раньше.

warn

Позволяет управлять выдачей предупреждений. Синтаксис:

#pragma warn +|-\.www

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

 

О различных ключах командной строки (и эквивалентных установках диалога Project Options) мы расскажем в разделе об особенностях компилятора.

Типичное применение препроцессорных директив

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

Предотвращение включения файлов

Иногда при использовании заголовков может происходить дублирование кода из-за повторного включения некоторого файла. (Допустим, у вас имеется исходный файл myprog.c, который подключает директивой # include два заголовка headerl.h и header2.h. Если, в свою очередь, оба этих файла подключают некоторый headerO.h, то последний будет дважды включен в исходный файл myprog.c. Это ни к чему, хотя обычно и не приводит к ошибке.)

Чтобы предотвратить повторное включение кода заголовочного файла, можно организовать контроль следующим образом (как говорят, “поставить часового”):

/*

** header0.h: Заголовочный файл, который может оказаться

** многократно включенным...

*/

#ifndef _HEADERO_H

#define _HEADERO_H

/* /

** Здесь идут макросы, определения типов

** и т.д. вашего заголовочного файла...

*/

*endif

Переключение разделов кода

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

/*

** Измените определение на 0, чтобы вернуться к старому варианту.

*/

*define NEW_VER I

#if NEW_VER /*

** Экспериментальный код.

*/

#else /*

** Старый код.

*/

*endif

Или, если не вводить дополнительный идентификатор:

/*

** Измените на 1, когда новый код будет отлажен.

*/

*if 0 /*

** Экспериментальный код.

*/

#else /*

* * Старый код.

*/

*endif

Отладочные диагностические сообщения

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

#define INFO(msg)

printf(#msg "\n")

#define DIAG(msg)

printf("File " _FILE_ " Line %d: " \ #msg "\n", _LINE_)

void SomeFunc(void)

{

INFO(Entering SomeFunc.);

/* Выводит информационное сообщение. */

if (someError)

DIAG(Error encountered!);

/* Выводит сообщение об ошибке. */

INFO(Exiting SomeFunc...) ;

}

Макрос assert()

В заголовочном файле assert.h определен макрос assert (), выполняющий примерно то же самое, что и показанный выше пример. Его “прототип” можно записать как

void assert(int test);

Макрос расширяется в оператор if, проверяющий условие test. Если его значение равно 0, печатается сообщение Assertion failed: с указанием имени файла и номера строки. Вот пример:

#include <assert.h>

assert(!someError) ;

Если перед включением 41айла assert.h определить символ ndebug, операторы assert () будут “закомментированы”.

Особенности C++Builder

В этом разделе мы обсудим два вопроса: особенности реализации языка в C++Builder и управление компилятором в IDE (диалог Project Options).

Расширения языка С

C++Builder поддерживает использование ряда ключевых слов, отсутствующих в стандартных ANSI C/C++. В таблице 4.4 перечислены все такие ключевые слова, которые могут применяться в программах на С. Многие из них могут записываться с одним или двумя начальными символами подчеркивания либо без них. Это сделано для того, чтобы можно было переопределить в препроцессоре какое-либо ключевое слово (например, форму без подчеркивания), сохранив возможность использования исходного слова (в форме с подчеркиванием). Рекомендую вам всегда пользоваться формой с двумя подчеркиваниями.

Таблица 4.4. Расширения набора ключевых слов языка С

Ключевые слова

Описание

asm

_asm

__asm

Позволяет вводить код ассемблера непосредственно в текст программы на C/C++. Синтаксис:

__asm операция операнды ;_ или перевод_ строки

Можно сгруппировать сразу несколько инструкций ассемблера в одном блоке asm:

__asm {

группа_ инструкций

}

cdecl

_cdecl

__cdecl

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

_Except

Служит для управления исключениями в программах на С.

_Export

__export

Служит для экспорта из DLL классов, функций или данных. (См. главу 2, где приведен пример построения DLL.)

_fastcall

__fastcall

Специфицирует функцию как вызываемую в соответствии с соглашением fascall (передача параметров в регистрах).

_Finally

Служит для управления исключениями в программах на С.

_Import

__import

Импортирует классы, функции или данные, находящиеся в DLL.

_Inline

Применяется для объявления в программах на С расширяемых функций (inline). Соответствует ключевому слову inline, которое имеется только в C++.

_Pascal

__pascal

___pascal

Специфицирует функцию как вызываемую в соответствии с соглашениями языка Pascal.

_stdcall

__stdcall

Специфицирует функцию как вызываемую в соответствии со стандартными соглашениями о вызове.

_Thread

Позволяет определять глобальные переменные, имеющие тем не менее отдельные копии для каждой из параллельно выполняющихся линий кода (threads).

_Try

Служит для управления исключениями в программах на С.

В следующих далее разделах мы дадим пояснения к некоторым из дополнительных ключевых слов.

Соглашения о вызове

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

void _stdcall SomeDLLFunc(void);

Рассмотрим по порядку различные протоколы вызова, поддерживаемые в C+4-Builder.

Несколько слов о стеке. На стеке сохраняется состояние процессора при прерываниях, распределяется память для автоматических (локальных) переменных, в нем сохраняется адрес возврата и передаются параметры процедур. Адресация стека (в 32-битных системах) производится посредством специальных адресных регистров процессора — указателя стека ESP и базы стека ЕВР. Адрес, на который указывает регистр ESP, называют вершиной стека. Основные операции при работе со стеком — это PUSH (втолкнуть) и POP (вытолкнуть). Операция PUSH уменьшает значение указателя стека и записывает последний по полученному адресу. Операция POP считывает значение со стека в свой

операнд и увеличивает указатель стека. (В 32-битном режиме адресации стек выравнивается по границе двойного слова, т. е. при операциях PUSH и POP значение ESP всегда изменяется на 4.) Таким образом, стек при заполнении расширяется сверху вниз, и вершина стека является на самом деле нижней его точкой, т. е. имеет наименьший адрес.

Посмотреть, что происходит на уровне машинного кода при различных типах вызовов, проще всего с помощью отладчика, о котором мы будем говорить в следующей главе. Можно также компилировать исходный модуль программы в код ассемблера, для чего придется запустить компилятор из командной строки с ключом -S:

bсс32.ехе -S myfile.c

Псевдопеременные

Псевдопеременные C++Builder служат представлением аппаратных регистров процессора и могут использоваться для непосредственной записи и считывания их содержимого. Регистровые псевдопеременные имеют такие имена:

_AL _AH _AX _ЕАХ

_BL _BH _ВХ _ЕВХ

_CL _CH _СХ __ЕСХ

_DL _DH _DX __EDX

_CS _DS _ES _SS

_SI _DI _ESI _EDI

_BP _SP _EBP _ESP

_FS _GS _FLAGS

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

Управление исключениями

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

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

В стандартном С нет средств для управления исключительными ситуациями, однако в C++Builder имеются три дополнительных ключевых слова (_try, _except и _finally), которые позволяют организовать в программе т. н. структурированное управление исключениями, которое отличается от стандартного механизма исключений, встроенного в C++.

Мы не будем сейчас подробно обсуждать механизмы обработки исключений, отложив это до тех времен, когда мы будем изучать специфические средства языка C++. Сейчас мы покажем только синтаксис _try/_except/_finally с краткими пояснениями.

Возможны две формы структурированной обработки исключений:

try

защищенный_блок_операторов except(выражение) блок_обработки исключения

либо

_try

защищенный_блок_опера торов

finally

блок_обработки_завершения

Защищенный блок содержит код, заключенный в фигурные скобки, который может возбудить исключение. При возбуждении исключения выполнение блока прерывается и управление передается обработчику, следующему за пробным блоком.. Блок обработки исключения исполняется только при возбужденном исключении и в зависимости от оценки выражения оператора _except. Это выражение должно принимать одно из трех значений:

EXCEPTION_EXECUTE_HANDLER EXCEPTION_CONTINUE_SEARCH EXCEPTION_CONTINUE_EXECUTION

Эти значения вызывают соответственно исполнение обработчика (блока __except), продолжение поиска обработчика (перевозбуждение исключения во внешнем блоке _try, если таковой имеется) или возобновление выполнения кода с той точки, где было возбуждено исключение.

Блок обработки завершения исполняется в любом случае, вне зависимости от того, возбуждено исключение или нет.

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

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

void RaiseException(DWORD ее, DWORD ef, DWORD na,

const DWORD *a) ;

где еc — код исключения,

ef — флаг исключения (EXCEPTION_CONTINUABLE либо EXCE.PTI-

ONONCONTINUABLE),

па — число аргументов,

а — указатель на первый элемент массива аргументов.

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

В этом разделе мы рассмотрим установки диалога Project Options, имеющие отношение к программам на С. В основном это будет касаться страниц Compiler и Advanced Compiler этого диалога. Он открывается выбором Project | Options в главном меню.

Страница Compiler

Эта страница диалога показана на рис. 4.1.

В нижней части страницы вы видите две кнопки: Full debug и Release. Первая из них выполняет все установки параметров, позволяющие в полной мере использовать возможности отладчика C++Builder; вторая запрещает генерацию какой-либо отладочной информации и оптимизирует код для получения максимальной скорости выполнения. При изучении языка вам лучше всего воспользоваться кнопкой Full debug и не задумываться больше об установках, влияющих на отладку и эффективность кода.

Рис. 4.1 Страница Compiler диалога Project Options

Коротко о разделах страницы Compiler.

Объем кода заголовочных файлов, включаемых в модуль исходный модуль, может достигать тысяч, если не десятков и сотен тысяч строк. К тому же часто эти заголовочные файлы включаются в каждый модуль проекта. Поскольку при разработке программы заголовочные файлы приходится изменять сравнительно редко (а стандартные заголовки вообще не меняются), имеет смысл компилировать все необходимые заголовки один раз и создать файл специального вида, который будет содержать всю необходимую “заголовочную” информацию в форме, обеспечивающей максимально быстрый доступ к ней. Компилятор C++Builder может генерировать такие файлы (с расширением .csm), •во много раз ускоряющие повторное построение проектов. Недостатком их можно считать разве что весьма большой размер — типичный файл прекомпилируемых заголовков может занимать от пяти до десяти мегабайт.

Если вы хотите отлаживать программу, то должны убедиться, что флажок Create debug information на странице Linker также установлен.

Страница Advanced Compiler

Эта страница (рис. 4.2) позволяет управлять деталями генерации объектного кода.

Рис. 4.2 Страница Advanced Compiler диалога Project Options

при сброшенном флажке все преобразования выполняются в строгом соответствии с правилами ANSI С. Correct Pentium FDIV генерирует код, исключающий возможность ошибки из-за дефекта в ранних версиях процессора Pentium. Соответствующие ключи командной строки — -f-, -ff и -fp.

Страница Directories/Conditionals

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

В поле Conditional defines можно определять символы C/C++, языка Object Pascal и компилятора ресурсов, которые будут, например, управлять директивами условной компиляции в исходных файлах. Для присвоения символам значений используется знак равенства. Можно ввести в это поле сразу несколько определений, отделяя их друг от друга точкой с запятой, например:

NDEBUG;ххх=1;yyy-YES

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

Рис. 4.3 Страница Directories/Conditronals

В командной строке символы определяются с помощью ключа -D:

bcc32 -DNDEBUG -Dxxx=l -Dyyy=YES ...

Мы немного рассказали о ключах командной строки компилятора не столько для того, чтобы вы умели запускать bcc32.ехе вручную, а чтобы дать вам некоторые г начальные сведения, которые помогут вам разбираться в Ьрг-файлах проектов C++Builder. Полное руководство по запуску компилятора из командной строки вы можете найти в оперативной справке в разделах command-line compiler и command-line options.

Заключение

Эта глава завершает предварительный курс по С, включая имеющиеся в C++Builder различные расширения языка. Прежде чем заняться вплотную объектно-ориентированным программированием на C++, мы считаем целесообразным познакомить вас с принципами отладки программ в IDE, чем мы и займемся в следующей главе.

Hosted by uCoz