методичка. Языки C, C++ (1). Методическое пособие для учащихся Москва 2017 Оглавление 2 удк ббк хлебников А. А
Скачать 2.43 Mb.
|
9. Препроцессор 9.1 Включение файла Включение файлов (помимо других полезных вещей) позволяет легко управлять наборами директив #define и объявлений. Любая строка вида или заменяется содержимым файла с именем filename. Если имя файла заключено двойные кавычки, то, как правило, файл ищется среди исходных файлов программы; если такового не оказалось, или имя файла заключено в угловые скобки < и >, то поиск осуществляется по определяемым реализацией правилам. Включаемый файл сам может содержать в себе строки #include. Часто исходные файлы начинаются с нескольких строк #include, ссылающихся на файлы, содержащие общие директивы #define, объявления extern или прототипы нужных библиотечных функций из заголовочных файлов вроде Оглавление 66 9.2 Макроподстановка Директива макроподстановки имеет вид: #define имя-замещающий-текст Макроподстановка используется для простейшей замены: во всех местах, где встречается имя, вместо него будет помещен замещающий текст. Имена в #define задаются по тем же правилам, что и имена обычных переменных. Замещающий текст может быть произвольным. Обычно замещающий текст целиком помещается в строке, в которой расположено слово #define, однако длинные определения можно разбивать на несколько строк, поставив в конце каждой продолжаемой строки обратную наклонную черту n. Область видимости имени, определенного директивой #define, простирается от определения до конца файла. В определении макроподстановки могут использоваться предшествующие ему макроопределения. Подстановка осуществляется только для тех имен, которые расположены вне текстов, заключенных в кавычки и не являются частью другого слова. Например, если YES определено с помощью директивы #define, то никакой подстановки в printf(”YES”) или в YESMAN выполнено не будет. Любое имя можно определить с произвольным замещающим текстом. #define forever for(;;) /* бесконечный цикл */ К примеру, он определяет новое слово forever для бесконечного цикла. Можно определить макрос с аргументами, чтобы замещающий текст варьировался в зависимости от задаваемых параметров. Например, определим max следующим образом: #define max (A, B) ((A) > (B) ? (A) : (B)) Хотя обращения к max выглядят как обычные обращения к функции, они будут вызывать только текстовую замену. Каждый формальный параметр (в данном случае A и B)будет заменяться соответствующим ему аргументом. Так, строка будет заменена на строку Оглавление 67 Поскольку аргументы просто подставляются в текст, указанное определение max подходит для данных любого типа, так что не нужно писать различные варианты max для данных разных типов, как это было бы в случае определения функции. Если вы внимательно проанализируете работу max, то обнаружите некоторые подводные камни. Выражения вычисляются дважды, и если они вызывают побочный эффект (из-за инкрементных операций или функций ввода-вывода), это может привести к нежелательным последствиям. Например, вызовет увеличение i и j дважды. Кроме того, следует позаботиться о скобках, чтобы обеспечить нужный порядок вычислений. Задумайтесь, что случится, если при определении #define square(x) x * x/* НЕВЕРНО */ вызвать square (z + 1). Тем не менее макросы имеют свои достоинства. Один из примеров можно найти в файле Функции в Действие #define можно отменить с помощью директивы #undef: #undef getchar Как правило, это делается, чтобы заменить макроопределение настоящей функцией с тем же именем. Имена формальных параметров не заменяются, если встречаются заключенных в кавычки строках. Однако, если в замещающем тексте перед формальным параметром стоит знак #, этот параметр будет заменен на аргумент, заключенный в кавычки. Это можно сочетать с конкатенацией строк, например, чтобы создать макрос отладочного вывода: #define dprint(expr) printf(#expr ” = %g\n”, expr) обращение к Оглавление 68 развернется в в результате конкатенации двух соседних строк, которая будет автоматически выполнена компилятором, получим Внутри фактического аргумента каждый знак” заменяется на n”, а каждая обратная наклонная черта n на nn, так что в результате подстановки получается правильная символьная константа. Оператор ## позволяет конкатенировать аргументы в макрорасширениях. Если в замещающем тексте параметр соседствует с ##, то он заменяется соответствующим ему аргументом, а оператор ## и окружающие его символы- разделители выбрасываются. Например, в макроопределении paste конкатенируются два аргумента #define paste(front, back) front ## back так что запись paste(name, 1) будет заменена на name1. Оглавление 69 9.3. Условная компиляция Работой препроцессора можно управлять с помощью условных инструкций. Они представляют собой средство для выборочного включения того или иного текста программы в зависимости от значения условия, вычисляемого во время компиляции. Вычисляется константное целое выражение, заданное в строке #if. Это выражение не должно содержать операторы sizeof, приведения типов и констант из перечислений enum. Если оно имеет ненулевое значение, то будут включены все последующие строки вплоть до ближайшей директивы #endif, #elif, или #else.(Директива препроцессора #elif действует как else if.) Выражение defined(имя) #if равно 1,если имя было определено, и 0 в противном случае. Например, чтобы застраховаться от повторного включения заголовочного файла hdr.h, его можно оформить следующим образом: #if !defined(HDR) #define HDR /* здесь содержимое hdr.h */ #endif При первом включении файла hdr.h будет определено имя HDR, а при последующих включениях препроцессор обнаружит, что имя HDR уже определено, и перескочит сразу на #endif. Этот прием может оказаться полезным, когда нужно избежать многократного включения одного и того же файла. Если им пользоваться систематически, то в результате каждый заголовочный файл будет сам включать заголовочные файлы, от которых он зависит, освободив от этого занятия пользователя. Вот пример цепочки проверок имени SYSTEM, позволяющей выбрать нужный файл для включения: #if SYSTEM == SYSV #define HDR ”sysv.h” #elif SYSTEM == BSD #define HDR ”bsd.h” #elif SYSTEM == MSDOS #define HDR ”msdos.h” #else Оглавление 70 #define HDR ”default.h” #endif #include HDR Инструкции #ifdef и #ifndef специально предназначены для проверки того, определено или нет заданное в них имя. И, следовательно, первый пример, приведенный выше для иллюстрации #if, можно записать и в таком виде: #ifndef HDR #define HDR /* здесь содержимое hdr.h */ #endif Оглавление 71 9.4 Директива #pragma Директивы pragma определяют функции компилятора для конкретного компьютера или операционной системы. #pragma string Каждая реализация C и C++ поддерживает некоторые функции, уникальные для хост-компьютера или операционной системы. Например, некоторые программы должны осуществлять точный контроль над областями памяти, в которых размещаются данные, или управлять способом получения параметров некоторыми функциями. Директивы #pragma позволяют компилятору использовать особенности конкретного компьютера или операционной системы, сохраняя при этом совместимость языков и С++. Директивы pragma характерны для конкретного компьютера или операционной системы по определению и обычно отличаются для каждого компилятора. Директивы pragma можно использовать в условных операторах для обеспечения новой функциональности препроцессора или для предоставления компилятору сведений, определенных реализацией. string –—это последовательность символов, которые предоставляют определенную инструкцию компилятора и аргументы, если таковые имеются. Символ решетки (#) должен быть первым отличным от пробела символом в строке, которая содержит директиву pragma; символы пробела могут разделять знак числа и слово pragma. После #pragma введите любой текст, который преобразователь может проанализировать как токены предварительной обработки. Аргумент #pragma подлежит расширению макроса. Если компилятор обнаруживает нераспознаваемую директиву pragma,он выдает предупреждение и продолжает компиляцию. Компиляторы Microsoft C и C++ распознают следующие директивы pragma. Оглавление 72 alloc_text auto_inline bss_seg check_stack code_seg comment component conform const_seg data_seg deprecated detect_mismatch fenv_access float_control fp_contract function hdrstop include_alias init_seg inline_depth inline_recursion intrinsic loop make_public managed сообщение omp once optimize pack pointers_to_members pop_macro push_macro region, endregion runtime_checks section setlocale strict_gs_check unmanaged vtordisp warning Оглавление 73 9.5 Директива#pragma pack Задает выравнивание упаковки для членов структуры, объединения и класса. Директива pack обеспечивает контроль на уровне объявления данных. В этом состоит отличие от параметра компилятора /Zp, который предоставляет контроль только на уровне модуля. Директива pack действует на первое объявление struct, union или class после этой директивы #pragma. Директива pack не действует на определения. При вызове директивы pack без аргументов для параметра n задается значение, указанное в параметре компилятора /Zp. Если этот параметр компилятора не указан, по умолчанию используется значение 8. При изменении выравнивания структурыона может занимать меньше места в памяти, но возможно снижение производительности или даже возникновение аппаратного исключения для не выровненного доступа. Поведение этого исключения можно изменить с помощью директивы SetErrorMode.show(необязательно) —Отображает текущее байтовое значение выравнивания упаковки. Значение отображается в предупреждении. push(необязательно) — Помещает текущее значение выравнивания упаковки внутренний стек компилятора и задает для текущего выравнивания упаковки значение n. Если значение n не указано, текущее значение выравнивания упаковки не помещается в стек. pop(необязательно) —Удаляет запись из вершины внутреннего стека компилятора. Если значение n не указано вместе с pop, то новым значением выравнивания упаковки становится значение упаковки, связанное с результирующей записьюв вершине стека. Если значение n указано (например, #pragma pack(pop,16)), n становится новым значением выравнивания упаковки. Если извлечение из стека производится вместе с идентификатором identifier (например, #pragma pack(pop, r1)),из стека извлекаются все записи,пока не будетнайдена запись identifier. Эта запись извлекается из стека, и новым значением выравнивания упаковки становится значение упаковки, связанное с результирующей записью в вершине стека. Если при извлечении из стека с идентификатором identifier этот идентификатор не найден ни в одной из записей стека, Оглавление 74 директива pop игнорируется. identifier(необязательно) —При использовании с директивой push присваивает имя записи во внутреннем стеке компилятора. При использовании с директивой pop записи из внутреннего стека извлекаются до тех пор, пока не будет удален идентификатор identifier; если идентификатор identifier во внутреннем стеке не найден, ничего не извлекается. n (необязательно) —Указывает значение(в байтах),используемое для упаковки. Если для модуля не задан параметр компилятора /Zp, по умолчанию для n используется значение 8. Допустимые значения: 1, 2, 4, 8 и 16. Выравнивание члена будет производиться по границе, кратной значению n или кратной размеру члена, в зависимости от того, какое из значений меньше. Значение #pragma pack(pop, identifier, n) не определено. Результат: 0 4 8 – 0 4 6 Оглавление 75 9.6 Директива #pragma section Создает раздел в OBJ-файле. Термины сегмент и раздел в этом разделе взаимозаменяемы. После определения раздел остается допустимым для остальной части компиляции. Однако следует использовать __declspec(allocate),так как иначе никакие данные не будут помещены в раздел. section-name —обязательный параметр, который будет именем раздела. Имя не должно конфликтовать со стандартными именами раздела. Список имен, которые не следует использовать при создании раздела, см. в разделе/SECTION. attributes —необязательный параметр, состоящий из одного или несколькихразделенных запятыми атрибутов, которые требуется присвоить разделу. Ниже перечислены возможные attributes. read (чтение) — Позволяет выполнять операции чтения данных. write (запись) — Позволяет выполнять операции записи данных. execute —Позволяет выполнять код. shared —Предоставляет совместный доступ к разделу всем процессам, загружающим образ. nopage —Отмечает раздел как невыгружаемый; используются для драйверов устройств Win32. nocache —Отмечает раздел как некэшируемый; используются для драйверов устройств Win32. discard —Отмечает раздел как удаляемый; используются для драйверов устройств Win32. remove —Отмечает раздел как нерезидентный; только драйверы виртуальных устройств (VxD). Оглавление 76 Если не задать атрибуты, раздел будет иметь атрибуты чтения и записи. В следующем примере первая инструкция определяет раздел и его атрибуты. Целое число j не помещается в mysec, поскольку оно не было объявлено с __declspec(allocate),j переходит в раздел данных. Целое число i переходит в mysec как результат атрибута класса хранения __declspec(allocate). /* pragma_section.cpp */ #pragma section(”mysec”, read, write) int j = 0 ; __declspec (allocate( ”mysec” )) int i = 0 ; int main (){} Оглавление 77 9.7. Директива#pragmacomment Вставляет запись комментария в объектный или исполняемый файл. #pragma comment( comment-type [, ”commentstring”] ) comment-type обозначает один из предопределенных идентификаторов(см.ниже), которые задают тип записи комментария. Необязательный параметр commentstring обозначает строковый литерал, который содержит дополнительную информацию (для некоторых типов комментариев). Поскольку параметр commentstring обозначает строковый литерал, он соблюдает все правила, действующие для строковых литералов в отношении escape-символов, внедренных кавычек (”) и конкатенации. compiler —Задает в объектном файле имя и номер версии компилятора. Эта запись комментария игнорируется компоновщиком. Если для этого типа записи будет указан параметр commentstring, компилятор выведет предупреждение. exestr —Задает в объектном файле commentstring, которая во время компоновки помещается в исполняемый файл. Эта строка не загружается в память вместе с исполняемым файлом, однако ее можно обнаружить при помощи программы, которая находит в файлах печатаемые строки. Записи комментариев этого типа позволяют, в частности, вставлять в исполняемый файл информацию о номере версии и т. д. Использовать параметр exestr не рекомендуется; в следующих выпусках он будет удален. Компоновщик не обрабатывает эту запись комментария. lib —Задает в объектном файле запись поиска библиотеки. Этот тип комментария должен сопровождаться commentstring с именем библиотеки, которую должен найти компоновщик. В нем также можно указать путь к ней. Имя библиотеки указывается в объектном файле после записей поиска по библиотекам по умолчанию. Компоновщик ищет эту библиотеку точно так же, как если бы она была указана в командной строке (если библиотека не была задана при помощи параметра /NODEFAULTLIB). В один и тот же исходный файл можно вставить несколько записей поиска библиотеки. В объектном файле они будут располагаться в том же порядке, что и в исходном. Оглавление 78 Если для вас важно, в каком порядке расположены и добавленные библиотеки, и библиотека по умолчанию, задайте при компиляции ключ /Zl. В этом случае в объектный модуль не будет вставлено имя библиотеки по умолчанию. Далее можно вставить еще одну директиву #pragma comment и с ее помощью добавить имя библиотеки по умолчанию уже после добавленной библиотеки. Библиотеки, добавленные при помощи этих директив, будут находиться в объектном модуле в том же порядке, в каком они указаны в исходном коде. linker — Задает параметр компоновщика в объектном файле. Благодаря этому параметр компоновщика можно не передавать из командной строки и не указывать в среде разработки, а задать непосредственно в комментарии. Например, в нем можно задать параметр /INCLUDE, чтобы принудительно включить символ: #pragma comment(linker, ”/INCLUDE:__mySymbol”) Идентификатору компоновщика могут передаваться только следующие параметры (comment-type) o /DEFAULTLIB o /EXPORT o /INCLUDE o /MANIFESTDEPENDENCY o /MERGE o /SECTION user —Вставляет в объектный файл комментарий общего рода. commentstring содержит текст комментария. Эта запись комментария игнорируется компоновщиком. Следующая директива pragma указывает компоновщику найти библиотеку EMAPI.LIB во время компоновки. Сначала компоновщик ищет ее в текущем рабочем каталоге, а затем по пути, заданном в переменной среды LIB. #pragma comment( lib, ”emapi” ) Оглавление 79 Следующая директива pragma указывает компилятору вставить в объектный файл имя и номер версии компилятора: #pragma comment( compiler ) #pragma comment( user, ”Compiled on ” __DATE_) |