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

Министерство  образования и  науки РФ

Государственное образовательное  учреждение высшего  профессионального  образования

ДАГЕСТАНСКИЙ  ГОСУДАРСТВЕННЫЙ  ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ

Кафедра ММвЭ 

Реферат

На  тему: Директивы процессора в С++  
 
 

Выполнил:

ст. курса, И912 гр. Келеметов С.Ш.

Руководитель  практики:

Зав. каф.  
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Махачкала 2011 г.

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

   Хотя  Turbo C++ использует использует интегрированный однопроходный компилятор как при работе в интегрированной среде разработки (IDE), так и при вызове компилятора из командной строки, полезно тем не менее сохранить терминологию, сохранившуюся от более ранних, многопроходных компиляторов. В случае последних на первом проходе обработки исходного текста программы выполняется подключение всех имеющихсявключаемых файлов, проверка всех условных директив компиляции, расширение всех имеющихся макросов и получение промежуточного файла для обработки последующими проходами компилятора. Посколькукак интегрированная среда разработки, так и версия командной строки Turbo C++ выполняют первый проход, не создаваяпри этом каких-либо промежуточных файлов, Turbo C++ включаетв себя независимый препроцессор, CPP.EXE, который имеет на выходе такой промежуточный файл. CPP полезен на стадии отладки, посколькупоказывает в чистом виде результаты работы директив включения, условных директив компиляции и сложных макрорасширений.

   CPP позволяет  обращение к документации по  нему в диалоговом режиме.

   Следующее обсуждение директив препроцессора, их синтаксис и семантика, применимы, следовательно, как к самому препроцессору CPP, так и к его функциям, встроенным в компиляторы Turbo C++.

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

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

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

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

   - Установка условной компиляции  для улучшения мобильности получаемых  кодов и для целей отладки.

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

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

   файл-для-препроцессора:

   группа

   группа:

   часть группы

   группа  часть-группы

   часть-группы:

   <лексемы-препроцессора>  новая-строка

   if-раздел

   управляющая строка

   if-раздел:

   if-группа <elif-группы> <else-группа> endif-строка

   if-группа:

   #if  выражение-типа-константы  новая-строка  <группа>

   #ifdef идентификатор новая-строка <группа>

   #ifndef идентификатор новая-строка <группа>

   elif-группы:

   elif-группа

   elif-группы  elif-группа

   elif-группа:

   #elif  выражение-типа-константы  <группа>

   else-группа:

   #else  новая-строка <группа>

   endif-строка:

   #endif новая-строка

   управляющая-строка:

   #include лексемы-препроцессора новая-строка

   #define идентификатор список-замены новая-строка

   #define идентификатор левая-круглая-скобка

   <список-идентификаторов>) список-замены новая-строка

   #undef идентификатор  новая-строка

   #line <лексемы-препроцессора> новая-строка

   #pragma <лексемы-препроцессора> новая-строка

   #pragma warn действие сокращение новая-строка

   #pragma inline  новая-строка

   ? новая-строка

   действие: одно из

   + - .

   сокращение:

   amb   ampapt   aus   big   cincpt

   def   dupelf   mod   par   piapro

   rch   retrng   rpt   rvf   sigstr

   stu   stvsus   ucp   use   volzst

   левая-круглая-скобка:

   символ  левой круглой скобки без предшествующих пробельных символов

   список-замены:

   <лексемы-препроцессора>

   лексемы-препроцессора:

   имя-заголовка (только для директивы #include)

   идентификатор (без различения ключевого слова)

   константа

   строковый-литерал

   операция

   пунктуатор

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

   имя-заголовка:

   <последовательность-символов-заголовка>

   последовательность-символов-заголовка:

   символ-заголовка

   последовательность-символов-заголовка  символ-заголовка

   символ-заголовка:

   любой символ из исходного множества символов, за исключением символа новой-строки (\n) или символа "больше чем" (>).

   новая-строка:

   символ  новой строки

   Пустая  директива #

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

   Директивы #define и #undef

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

   Простые макросы #define

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

   #define идентификатор_макроса <последовательность_лексем>

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

   Любые вхожденияидентификаторамакроса, обнаруженное в литеральных строках, символьных константах или комментариях расширению не подлежат.

   Пустая  последовательность лексем позволяетэффективное удаление всех найденных идентификаторов макросов из исходного кода:

   #define HI "Добрый день!"

   #define empty

   #define NIL ""

   ...

   puts(HI);   /* расширяется в: puts("Добрый день!"); */

   puts(NIL);   /* расширяется в: puts(""); */

   puts("empty"); /* расширения empty не происходит ! */

   /* расширение empty не будет выполнено и в комментариях! */

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

   #define GETSTD #include <stdio.h>

   ...

   GETSTD   /* ошибка компиляции */

   GETSTD будет расширен в #include<stdio.h>. Однако, препроцессор не станет сам обрабатывать эту вполне допустимую в других условиях директиву, а передаст ее в таком виде компилятору. Компилятор воспримет#include <stdio.h> как недопустимый ввод. Макрос не может быть расширен во время собственногорасширения. Поэтому выражения типа #define A A недопустимы вследствие неопределенности результата.

   Директива #undef

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

   #undef идентификатор_макроса

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

   Макрорасширения внутри строк #undef не выполняются.

   Состояние определенности и неопределенности является важным свойством идентификатора, независимо от его фактического определения. Условные директивы препроцессора #ifdef и #ifndef, которые служат для проверки того, является ли идентификатор в текущий момент определенным, или нет, представляют собой гибкий механизм управления многими аспектами компиляции.

   После того, как идентификатор макроса стал

   неопределенным,  он  может бытьдалеепереопределендирективой

   #define, с использованием той же самой или другой последовательности лексем.

   #define BLOCK_SIZE 512

   ...

   buff = BLOCK_SIZE*blks; /* расширяется в: 512*blks */ ...

   #undef BLOCK_SIZE

   /* использование  BLOCK_SIZE теперь невозможно - это "неизвестный"  препроцессору идентификатор */

   ...

   #define BLOCK_SIZE 128 /*переопределение размера блока*/

   ...

   buf = BLOCK_SIZE*blks; /* расширяется в:  128*blks */

   ...

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

   #ifndef BLOCK_SIZE

   #define BLOCK_SIZE 512

   #endif

   Если  идентификатор BLOCK_SIZE в текущий момент определен, то средняя строка не обрабатывается препроцессором; в противном же случае выполняется определение средней  строки.

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

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

   #define BLOCK_SIZE = 512 /* почему последовательность лексем включает символ = */

   Опции -D и -U

   Определение и отмена определения идентификаторов  выполняется также при помощи опций компилятора командной  строки - D и -U (см. Главу 4,"Компилятор командной строки" в Руководстве  пользователя). Идентификаторы могут  быть определены, но не могут бытьявно отменены, при помощи меню интегрированной среды разработки Options \! Compiler \! Defines (см. Главу 1,"Справочник по интегрированнойсредеразработки", также в Руководстве пользователя.)

   Командная строка

   tcc -Ddebug=1; paradox=0; X -Umysym myprog.c

   эквивалентна помещению в программу строк:

   #define debug 1

   #define paradox 0

   #define X

   #undef mysym

   Ключевые  слова и защищенные слова

   Допустимо, но не рекомендуется, использовать ключевые слова Turbo C++ в качестве идентификаторов макросов:

   #define int long /* допустимо, но может привести к катастрофическим последствиям */

   #define INT long /* допустимо и, вероятно, полезно */

   Следующие предопределенные глобальные идентификаторы не могут появляться непосредственно  следом за директивами #defineили #undef:

   __STDC__ __DATE__

   __FILE__ __TIME__

   __LINE__

   Отметим наличие в этих именах ведущих  и хвостовых двойных символов подчеркивания.

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

   Для определения макросов с параметрами  используется следующий синтаксис:

   #define идентификатор_макроса(<список-аргументов>) последовательность-лексем

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

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

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

   идентификатор-макроса<пробельный-символ>(<список-фактическихаргументов>)

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

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

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

   #define CUBE(x)  ((x)*(x)*(x))

   ...

   int n,y

   n = CUBE(y):

   дает  в результате следующую замену:

   n = ((y)*(y)*(y));

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

   #define SUM ((a) + (b))

   ...

   int i,j,sum;

   sum = SUM(i,j);

   при расширении даст sum = ((i)+(j)). Причина кажущегося избытка круглых скобок станет очевидной, если рассмотреть пример:

   n = CUBE(y+1);

   Без внутренней пары круглых скобок в определении  расширение даст запись: n=y+1*y+1*y+1, что  при лексическом анализе равно:

   n = y + (1*y) + (1*y) + 1; // если y не равен 0 или -3, то в // куб возводится (y+1) !

   Как и в случае простых макроопределений, производится повторное сканирование текста для определения необходимости  повторных макрорасширений получившихся макро-идентификаторов.

   При использовании  макросов со спискамиаргументов следует обратить внимание на следующие моменты:

   1. Вложенные  круглые скобки и запятые:

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

   #define ERRMSG(x, str) showerr("Error",x,str)

   #define  SUM(x,y) ((x) + (y))

   ...

   ERRMSG(2, "Press Enter, then Esc");

   // расширится в: showerr("Error",2,"Press Enter, then Esc"); */ return SUM(f(i,j), g(k.l));

   // расширится  в: return ((f(i,j)) + (g(k,l))); */

   2. Склеивание лексем при помощи ##: можно выполнить склеивание (слияние)  двух лексем, разделив их символами  ## (и плюс опциональными пробельными  символами с каждой стороны). Препроцессор  удаляет пробельные символы и##, объединяядве отдельные лексемыв одну новуюлексему. Это средство можно использовать для конструированияидентификаторов; например, выполнив определение

   #define VAR(i,j) (i##j)

   и затем вызвав VAR(x,6), можно получить расширение (x6). Этот метод заменяет старый (не обеспечивающий мобильность кода) метод использования (i/**/j).

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

   #define TRACE(flag) printf(#flag "=%d\n",flag)

   фрагмент кода

   int highval = 1024;

   TRACE(highval);

   станет равным

   int highval = 1024;

   printf("highval" "= %d\n", highval);

   что в  свою очередь будет рассматриваться  как

   int highval = 1024;

   printf("highval=%d\n", highval);

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

   #define WARN "фактически это одно\

   строчное  сообщение"

   ...

   puts(WARN);

   /* на  экране будет: фактически это  однострочное сообщение */

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

   int cube(int x) (*

   return x*x*x;

   *)

   #define CUBE(x) ((x)*(x)*(x))

   ...

   int b =0, a = 3;

   b = cube(a++);

   /* cube() передается фактический аргумент = 3; поэтому b = 27, и теперь a = 4 */

   a = 3;

   b = CUBE(a++);

   /* расширяется  в:  ((a++)*(a++)*(a++)); и теперь a = 6 */

   Итоговое  значение b зависит от того, что компилятор делает с расширенным выражением.

   Включение файлов директивой #include

   Директива #include подключает к исходному коду заданные в ней файлы, известные как включаемые файлы, файлы заголовковили заголовки. Синтаксис этой директивы имеет три формы:

   #include <имя_заголовка>

   #include "имя_заголовка"

   #include идентификатор_макроса

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

   Третий  вариант записи предполагает, что  ни символ <, ни символ " не являются первым не-пробельным символом, следующим за #include; кроме того, предполагается, что существует такое макроопределение, которое расширит идентификатор макроса в допустимое, следующее за разделителем имя заголовка в формате либо <имя_заголовка>, либо "имя_заголовка".

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

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

   Если  поместить в имя_заголовка полное имя пути доступа к файлу, то поиск файла будет выполнентольков указанной таким образом директории.

   Различиемежду форматами <имя_заголовка> и "имя_заголовка" заключается в алгоритме поиска включаемого файла, применяемом в каждом случае; эти алгоритмы описаны в следующих двух разделах.

   Поиск файла заголовка при формате <имя_заголовка>

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

   Поиск файла заголовка при формате "имя_заголовка"

   Вариант "имя_заголовка" задает включаемый файл, создаваемый пользователем; сначала выполняется его поиск в текущей директории (обычно в той директории, в которой находится исходный компилируемый файл). Если там файл не найден, то поиск продолжается во всех включаемых директориях, как в случае формата <имя_заголовка>.

   Приводимые  ниже примеры поясняют описанные  различия:

   #include <stdio.h>

   /* заголовок  из стандартной включаемой директории */

   #define  myinclude"c:\tc\include\mystuff.h"

   /* Примечание: здесь допустимы одинарные символы  обратной

   наклонной черты; в операторе С пишется:

   "c:\\tc\\include\\mystuff.h"  */

   #include myinclude

   /* макрорасширение */

   #include "myinclude.h"

   /* макрорасширение  отсутствует */

   После расширения второй оператор#include заставит препроцессор искать нужный файл в C:\TC\INCLUDE\mystuff.h, и нигде более. Третий пример #includeзаставляет препроцессор выполнить поискmyinclude.h сначала в текущей директории, а затем во включаемых директориях.

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