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

 

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

Основные директивы препроцессора

#include — вставляет текст из указанного файла
#define — задаёт макроопределение (макрос) или символическую константу
#undef — отменяет предыдущее определение
#if — осуществляет условную компиляцию при истинности константного выражения
#ifdef — осуществляет условную компиляцию при определённости символической константы
#ifndef — осуществляет условную компиляцию при неопределённости символической константы
#else — ветка условной компиляции при ложности выражения
#elif — ветка условной компиляции, образуемая слиянием else и if
#endif — конец ветки условной компиляции
#line — препроцессор изменяет номер текущей строки и имя компилируемого файла
#error — выдача диагностического сообщения
#pragma — действие, зависящее от конкретной реализации компилятора.

Директива #include

Директива #include позволяет включать в текст программы указанный файл. Если заголовочный файл содержит описание библиотечных функций и находится в папке компилятора, он заключается в угловые скобки <>.
Если файл находится в текущем каталоге проекта, он указывается в кавычках «». Для файла, находящегося в другом каталоге необходимо в кавычках указать полный путь.

#include <stdio.h>
#include «func.c»

Директива #define позволяет вводить в текст программы константы и макроопределения.
Общая форма записи

#define Идентификатор Замена

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

1
2
3
4
5
6
7
8

#include <stdio.h>
#define A 3
int main()
{
  printf(«%d + %d = %d», A, A, A+A); // 3 + 3 = 6
  getchar();
  return 0;
}

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

  • U или u представляет целую константу в беззнаковой форме (unsigned);
  • F (или f) позволяет описать вещественную константу типа float;
  • L (или l) позволяет выделить целой константе 8 байт (long int);
  • L (или l) позволяет описать вещественную константу типа long double

#define A 280U   // unsigned int
#define B 280LU  // unsigned long int
#define C 280    // int (long int)
#define D 280L   // long int
#define K 28.0   // double
#define L 28.0F  // float
#define M 28.0L  // long double

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

идентификатор(аргумент1, …, агрументn)

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

Пример на Си: Вычисление синуса угла

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define PI 3.14159265
#define SIN(x) sin(PI*x/180)
int main()
{
  int c;
  system(«chcp 1251»);
  system(«cls»);
  printf(«Введите угол в градусах: «);
  scanf(«%d», &c);
  printf(«sin(%d)=%lf», c, SIN(c));
  getchar(); getchar();
  return 0;
}

Результат выполнения
Директива define

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

Однако при использовании таких макроопределений следует соблюдать осторожность, например

1
2
3
4
5
6
7
8
9
10
11
12
13

#include <stdio.h>
#define sum(A,B) A+B
int main()
{
  int a, b, c, d;
  a = 3; b = 5;
  c = (a + b) * 2; // c = (a + b)*2
  d = sum(a, b) * 2; // d = a + b*2;
  printf(» a = %d\n b = %d\n», a, b);
  printf(» c = %d \n d = %d \n», c, d);
  getchar();
  return 0;
}

Результат выполнения:
Использование макроопределений define
По умолчанию текст макроопределения должен размещаться на одной строке. Если требуется перенести текст макроопределения на новую строку, то в конце текущей строки ставится символ «обратный слеш» — \.

1
2
3
4
5
6
7
8
9
10
11
12
13
14

#include <stdio.h>
#define sum(A,B) A + \
                 B
int main()
{
  int a, b, c, d;
  a = 3; b = 5;
  c = (a + b) * 2; // c = (a + b)*2
  d = sum(a, b) * 2; // d = a + b*2;
  printf(» a = %d\n b = %d\n», a, b);
  printf(» c = %d \n d = %d \n», c, d);
  getchar();
  return 0;
}

Кроме того, директива #define позволяет замещать часть идентификатора. Для указания замещаемой части используется ##.

1
2
3
4
5
6
7
8
9

#include <stdio.h>
#define SUM(x,y) (a##x + a##y)
int main()
{
  int a1 = 5, a2 = 3;
  printf(«%d», SUM(1, 2)); // (a1 + a2)
  getchar();
  return 0;
}

Результат выполнения:
Использование ## в директиве #define

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

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

1
2
3
4
5
6
7

#if константное выражение
   группа операций
#elif константное выражение
   группа операций
#else
   группа операций
#endif

Отличие директив  #ifdef/#ifndef заключается в том, что константное выражение может быть задано только с помощью #define.

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

Пример

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

#include <stdio.h>
#include <stdlib.h>
#define P 2
int main()
{
  system(«chcp 1251»);
  system(«cls»);
#if P==1
  printf(«Выполняется ветка 1»);
#elif P==2
  printf(«Выполняется ветка 2, P=%d», P);
#else
  printf(«Выполняется другая ветка, P=%d», P);
#endif
  getchar();
  return 0;
}

Результат выполнения
Условная компиляция

Назад: Язык Си

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

Последнее обновление: 07.01.2023

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

Прежде всего это такие директивы как #if/#else/#endif, действие которых напоминает условную конструкцию if:

#if условие
исходный_код
#endif

Если условие возвращает ненулевое значение (то есть оно истинно), то в итоговый исходный файл вставляется исходный код, который расположен между директивами
#if и #endif:

#include <stdio.h>
#define N 22

int main(void)
{
#if N==22
	printf("N=22");
#endif
	return 0;
}

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

#include <stdio.h>
#define N 22
 
int main(void)
{
#if N==22
    printf("N=22");
#else
    printf("N is undefined");
#endif
    return 0;
}

С помощью директивы #elif можно проверять дополнительные условия:

#include <stdio.h>
#define N 24

int main(void)
{
#if N==22
	printf("N = 22");
#elif N==24
	printf("N=24");
#else
	printf("N is undefined");
#endif
	return 0;
}

#ifdef

С помощью директивы #ifdef можно проверять, определен ли идентификатор, и если он определен, вставлять в исходный код определенный текст:

#include <stdio.h>
#define DEBUG

int main(void)
{
#ifdef DEBUG
	printf("Debug mode");
#endif
	return 0;
}

Обратным действием обладает директива #ifndef — она включает текст, если идентификатор не определен:

#include <stdio.h>
//#define DEBUG

int main(void)
{
#ifndef DEBUG
	printf("Production mode");
#else
	printf("Debug mode");
#endif
	return 0;
}

Если нам одновременно надо проверить значения двух идентификаторов, то можно использовать специальный оператор defined:

#include <stdio.h>
#define BETA
#define DEBUG

int main(void)
{
#if defined DEBUG && !defined BETA
	printf("debug mode; final version");
#elif defined DEBUG && defined BETA
	printf("debug mode; beta version");
#else
	printf("undefined mode");
#endif
	return 0;
}

From Wikipedia, the free encyclopedia

In computer programming, conditional compilation is a compilation technique which results in an executable program that is able to be altered by changing specified parameters. This technique is commonly used when these alterations to the program are needed to run it on different platforms, or with different versions of required libraries or hardware.

Many programming languages support conditional compilation. Typically compiler directives define or «undefine» certain variables; other directives test these variables and modify compilation accordingly. For example, not using an actual language, the compiler may be set to define «Macintosh» and undefine «PC», and the code may contain:

(* System generic code *)
if mac != Null then
    (* macOS specific code *)
else if pc != Null
    (* Windows specific code *)

In C and some languages with a similar syntax, this is done using an ‘#ifdef’ directive.

A similar procedure, using the name «conditional comment», is used by Microsoft Internet Explorer from version 5 to 9 to interpret HTML code. There is also a similar proprietary mechanism for adding conditional comments within JScript, known as conditional compilation.[1]

Criticism[edit]

When conditional compilation depends on too many variables, it can make the code harder to reason about as the number of possible combinations of configuration increases exponentially.[2][3][4] When conditional compilation is done via a preprocessor that does not guarantee syntactically correct output in the source language, such as the C preprocessor, this may lead to hard-to-debug compilation errors,[5][6][7] which is sometimes called «#ifdef hell.»[8][9]

References[edit]

  1. ^ «Conditional Compilation». Microsoft Corporation. Archived from the original on 2008-09-06. Retrieved 2011-11-27.
  2. ^ Gazzillo, Paul; Wei, Shiyi (2019-05-27). «Conditional Compilation is Dead, Long Live Conditional Compilation!» (PDF). ICSE-NIER ’19: Proceedings of the 41st International Conference on Software Engineering: New Ideas and Emerging Results. 2019 IEEE/ACM 41st International Conference on Software Engineering: New Ideas and Emerging Results (ICSE-NIER). Montreal, QC, Canada: IEEE Press. pp. 105–108. doi:10.1109/ICSE-NIER.2019.00035. ISBN 978-1-7281-1758-4. Archived (PDF) from the original on 2022-11-07. Retrieved 2023-01-21.
  3. ^ Meinicke, Jens; Thüm, Thomas; Schröter, Reimar; Benduhn, Fabian; Leich, Thomas; Saake, Gunter (2017). Meinicke, Jens; Thüm, Thomas; Schröter, Reimar; Benduhn, Fabian (eds.). Quality Assurance for Conditional Compilation. pp. 131–139. doi:10.1007/978-3-319-61443-4_12. ISBN 978-3-319-61443-4. Retrieved 2023-01-21.
  4. ^ «compiler — How does conditional compilation impact product quality, security and code complexity?». Software Engineering Stack Exchange. Retrieved 2023-01-21.
  5. ^ Le, Duc; Walkingshaw, Eric; Erwig, Martin (2011-09-18). #ifdef confirmed harmful: Promoting understandable software variation. 2011 IEEE Symposium on Visual Languages and Human-Centric Computing (VL/HCC). pp. 143–150. doi:10.1109/VLHCC.2011.6070391. ISBN 978-1-4577-1246-3.
  6. ^ «conditional compilation — Why should #ifdef be avoided in .c files?». Stack Overflow. Retrieved 2023-01-21.
  7. ^ «c++ — Dos and Don’ts of Conditional Compile». Stack Overflow. Retrieved 2023-01-21.
  8. ^ Preschern, Christopher (2019-07-03). Patterns to escape the #ifdef hell (PDF). Proceedings of the 24th European Conference on Pattern Languages of Programs. EuroPLop ’19. New York, NY, USA: Association for Computing Machinery. pp. 1–12. doi:10.1145/3361149.3361151. ISBN 978-1-4503-6206-1. Archived (PDF) from the original on 2022-12-21.
  9. ^ «Living in the #ifdef Hell». www.cqse.eu. 28 October 2015. Archived from the original on 2022-11-28. Retrieved 2023-01-21.







Deutsch (de)
English (en)



suomi (fi)
français (fr)










русский (ru)









Что такое условная компиляция?

Условная компиляция — это компиляция или пропуск части исходного кода в зависимости от того, существует условие или нет.

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

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

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

Free Pascal поддерживает четыре различных стиля условной компиляции:

  • Turbo Pascal и ранние директивы стиля Delphi
  • директивы стиля Mac Pascal
  • Современные директивы в стиле Free Pascal и Delphi
  • Макросы времени компиляции

Note-icon.png

Примечание: здесь синтаксис не чувствителен к регистру, поскольку соответствует всему синтаксису Pascal. Мы будем использовать как строчные, так и прописные примеры. Мы покажем вам разницу между режимами и как их эффективно использовать.

Директивы в стиле Turbo Pascal

Директивами в стиле Turbo Pascal являются {$DEFINE}, {$IFDEF}, {$ENDIF}, {$IFNDEF}, {$IFOPT}, {$ELSE}, {$ELSEIF} и {$UNDEF}.

Мы опишем директивы в контексте стиля. Некоторые определения имеют расширенное значение в другом стиле.

Это означает, что в дальнейшем мы можем расширить значение некоторых директив, таких как, например, {$DEFINE}, в контексте макросов.

$define

Директива {$DEFINE} просто объявляет символ, который мы позже можем использовать для условной компиляции:

{$DEFINE name} //Это объявляет символ под названием "name"

Обратите внимание, что вы также можете объявить символ из командной строки или , например, -dDEBUG IDE, который будет являться эквивалентом командной строки

в исходном коде.

$undef

Директива {$UNDEF} отменяет объявление ранее определенного символа. Вот пример, который автор использует на практике:

// Некоторый старый исходный код, "загрязненый" обилием {$IFDEF FPC}, который больше 
// не нуждается в зависимости от версии Delphi, с которой он должен быть совместим. 
// Я всегда проверяю это, пробуя это поверх программы или модуля:
{$IFDEF FPC}
  {$MODE DELPHI}
  {$UNDEF FPC}
  {$DEFINE VER150} 
 // код теперь будет компилироваться так, как если бы это был Delphi 7, при условии, что исходный код Delphi действительно был написан для Delphi 7 и выше.
{$ENDIF}

$ifdef и $endif

Самый простой способ объявить блок условного кода так:

unit cross;
{$IFDEF FPC}{$MODE DELPHI}{$ENDIF}

Приведенный выше пример довольно распространен для исходного кода, который должен компилироваться как в Delphi, так и в Free Pascal.

Если компилятором является Delphi, то ничего не делается, но если компилятором является Free Pascal, он переключит Free Pascal для компиляции и использования режима синтаксиса Delphi.

Этот условный символ «FPC» объявлен в системе — там их длинный список. Синтаксис блоков {$IFDEF} и {$ENDIF} является симметричным: каждый {$IFDEF} имеет свой собственный {$ENDIF}.

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

{$IFDEF FPC эта часть специфична для Free Pascal}
// некий Free Pascal специфичный код
{$ENDIF Free Pascal специфичный код}

Warning-icon.png

Предупреждение: Эта особенность комментариев часто не совсем понятна. Некоторые люди — как и в более старой версии этой записи вики — предположили, что вы можете вкладывать {$IFDEF}, потому что компилятор, кажется, принимает синтаксис. Но первое неверно, а второе верно: да, компилятор принимает приведенный ниже синтаксис, но это не вложенное {$IFDEF}, а одиночное условие {$IFDEF}, а остальное — комментарий!

Приведенный ниже код выполнит writeln тогда и только тогда, когда будет объявлен {$define red}. В этом примере {$ifdef blue} является комментарием! Даже если {$define blue} допустим.

// программа формально будет скомпилирована, но полностью неработоспособна
{$define blue}  
begin
{$ifdef red or $ifdef blue}// все после red - это комментарий 
  writeln ('red or blue'); // этот код никогда не выполнится
{$endif red or blue}       // все, что объявлено после $endif, является комментарием.
end.

$ifndef

Это противоположно {$IFDEF}, и ниже лежащий код выполнится, если объявленное условие не выполняется. Простой пример:

{$IFNDEF FPC эта часть не для Free Pascal}
// некий конкретный код, который Free Pascal не должен компилировать
{$ENDIF код для других компиляторов, кроме Free Pascal}

$else и $elseif

{$ELSE} используется для компиляции кода, который не принадлежит блоку кода, который объявлен соответствующим {$IFDEF}. Он также действителен в контексте {$IFOPT}, {$IF} или {$IFC}, которые мы обсудим позже.

{$IFDEF red}  
   writeln('Red объявлен');  
{$ELSE  no red}  
  {$IFDEF blue}  
   writeln('Blue объявлен, а red  - не объявлен');  
  {$ELSE no blue}  
  writeln('Ни red, ни blue не объявлены'); 
  {$ENDIF blue}  
{$ENDIF red}

Такие вложенные условные выражения, написанные в приведенном выше синтаксисе, могут стать очень путанными и нечитаемыми. К счастью, мы можем упростить это, используя {$ELSEIF}. Код ниже является расширенным эквивалентом первого примера:

{$IF Defined(red)}  
  writeln('Red объявлен');  
{$ELSEIF Defined(blue)}  
  writeln('Blue объявлен');  
{$ELSEIF Defined(green)}  
  writeln('Green объявлен');   
{$ELSE}
  writeln('Ни red, ни blue, ни green не объявлены. Должен быть black...или что-нибудь еще...');
{$ENDIF}

Как вы можете видеть, это гораздо более читабельно.

$ifopt

С помощью {$IFOPT} мы можем проверить, задана ли определенная опция компиляции.

Из руководства по программированию:

 {$IFOPT switch} скомпилирует текст, который следует за ним, если переключатель switch 
 в данный момент будет находиться в указанном состоянии. Если он не находится в указанном состоянии, 
 то компиляция продолжится после соответствующей директивы {$ELSE} или {$ENDIF}.

Например:

 {$IFOPT M+}  
   Writeln('Compiled with type information');  
 {$ENDIF}

Скомпилирует оператор Writeln, только если включена генерация информации о типе.

Note-icon.png

Примечание: директива {$IFOPT} принимает только короткие опции, т.е. {$IFOPT TYPEINFO} не будет принята.

Обычно этот пример используется для проверки, задан ли режим DEBUG:

{$IFOPT D+}{$NOTE debug mode is active}{$ENDIF}

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

# ----------------------
# Defines (preprocessor)
# ----------------------
#
# nested #IFNDEF, #IFDEF, #ENDIF, #ELSE, #DEFINE, #UNDEF are allowed
#
# -d is the same as #DEFINE
# -u is the same as #UNDEF
#
#
# Some examples (for switches see below, and the -? help pages)
#
# Try compiling with the -dRELEASE or -dDEBUG on the command line
#
# For a release compile with optimizes and strip debug info
#IFDEF RELEASE
  -O2
  -Xs
  #WRITE Compiling Release Version
#ENDIF

Как не нужно делать

Что не так с этим кодом? Вы можете это заметить?

var
  MyFilesize:
  {$ifdef Win32} 
    Cardinal 
  {$else}
    int64
  {$endif}

Вот ответ:

  • Free Pascal компилируется для большего количества типов процессоров, чем 32 и 64 bit, например, для 8 и 16 бит.
  • на большинстве 64-битных платформ максимальный размер файла — это QWord, а не Int64.

Этот программист попал в ловушку, которая распространена: если вы используете объявление, убедитесь, что ваша логика надежна. В противном случае такой код может легко вызвать несчастные случаи. Компилятор не будет ловить ваши логические ошибки!

Всегда хорошо осознавать такие вещи, тем более что такие вещи легко исправить.

var
  MyFilesize:
  {$if defined(Win32)} 
    Cardinal 
  {$elseif defined(Win64)}
    Qword;
  {$else}
     {$error этот код написан для win32 или win64}
  {$endif}

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

var
  MyFilesize:NativeUint;

Что не так с этим кодом? Вы можете это заметить?

  
{$IFDEF BLUE AND $IFDEF RED} Form1.Color := clYellow; {$ENDIF}
{$IFNDEF RED AND $IFNDEF BLUE} Form1.Color := clAqua; {$ENDIF}

Вот ответ:

  • Ну, я уже написал комментарий, который предупредил вас .. так что посмотрите на предупреждение …. Вы должны быть в состоянии определить это …
  • Директивы компилятора переопределяют компилятор … будьте осторожны с этим топором Юджина.
Directives, definitions and conditionals definitions
global compiler directives • local compiler directives

Conditional Compiler Options • Conditional compilation • Macros and Conditionals • Platform defines
$IF

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

#include <stdio.h>
 
int main(void)
{
    printf("Hello, World!\n");
    return 0;
}

после обработки
текстовым препроцессором вместо первой строчки будет подставлено содержимое
файла stdio.h. В данном
примере это нужно, чтобы мы могли использовать библиотечную функцию printf() в своей
программе. Но в самом файле stdio.h нет реализации
этой функции, а только ее описание (прототип) в виде:

int
printf(const char* format, …);

Кстати, если
вместо директивы прописать в явном виде сигнатуру функции printf():

int printf(const char* format, ...);
 
int main(void)
{
    printf("Hello, World!\n");
    return 0;
}

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

В целом, этого
понимания работы директивы #include вполне достаточно для
программирования на Си. Отмечу только один нюанс. Имя файла после директивы #include можно заключать
или в угловых скобках, или в кавычках. Например, если ее записать в виде:

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

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

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

Например,
давайте в нашем проекте создадим подкаталог tmp и в нем
разместим файл с именем printf.h и содержимым:

int printf(const char *format, ...);

Затем, подключим
этот файл в программе следующим образом:

#include "tmp/printf.h"
 
int main(void)
{
    printf("Hello, World!\n");
    return 0;
}

Как видите, мы
использовали здесь двойные кавычки, т.к. файл printf.h наш собственный
и, кроме того, указали подкаталог tmp относительно рабочего каталога,
т.к. файл printf.h находится в
этом подкаталоге.

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

Следующий набор
директив, который мы разберем, — это, так называемые, директивы условной компиляции:

#if,
#endif, #elif, #else, #ifdef, #ifndef, #elifdef, #elifndef

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

#define LANG_C
 
#if defined(LANG_C)
#   include <stdio.h>
#else
#   include <iostream>
#endif
 
int main(void)
{
         int x=5;
#ifdef LANG_C
         printf("%d\n", x);
#else
         std::cout << x << std::endl;
#endif
 
    return 0;
}

Смотрите,
вначале определено макроимя LANG_C с помощью
директивы #define. Затем,
прописана директива #if, в которой проверяется условие:
определено ли макроимя LANG_C в текущем
модуле. Если это так (как в нашем примере), то макропроцессор оставляет в
программе все, что записано после этой директивы либо до следующей условной
директивы, либо до директивы #endif. В приведенном примере, остается
строчка «#   include <stdio.h>» и
удаляется строка «#   include <iostream>».
Соответственно, директива #include также, затем, обрабатывается
макропроцессором. В итоге, после обработки, у нас получается следующий текст программы:

#   include <stdio.h>
 
int main(void)
{
         int x=5;
         printf("%d\n", x);
 
    return 0;
}

Разумеется,
директива #include здесь также
впоследствии преобразуется макропроцессором. А директива #ifdef – это
сокращенный вариант записи конструкции #if defined.

По сути,
директивы условной компиляции #if, #else, #endif работают
подобно условным операторам if-else, о которых мы с
вами уже говорили. Но, конечно же, есть и отличия. Первый важный момент: в
условиях директив можно использовать исключительно целочисленные литералы и
макроимена. С этими элементами можно выполнять все булевы операции сравнения:

==, !=, <,
>, <=, >=

логические
связки:

&&, ||,
!

все бинарные арифметические
и битовые операции:

+, -, *, /, %,
&, |, ^

и применять
оператор defined, которые
возвращает 1, если указанное макроимя существует и 0 – в противном случае. Есть
еще несколько экзотических конструкций, вроде условной тернарной операции,
которые допустимо прописывать в условиях директив, но в основном используются
те операции, что перечислены выше. Обратите внимание, никаких переменных,
функций и прочих конструкций, значение которых определяется в процессе работы
программы, здесь применять нельзя.

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

Третий важный
момент. Директивы препроцессора анализируют программу как текст (на уровне
лексем). Это означает, что они не учитывают области видимости: локальные,
глобальные и т.п. Поэтому все директивы принято записывать с самого начала
строки (с левого края). В частности, именно поэтому символ # у директив include записан на
одном уровне с другими директивами, т.к. никакого реального вложения здесь нет,
и это мы подчеркиваем оформлением. То же самое при записи директив внутри
функции main(). Для всех
этих директив функции не имеют никакого значения – это просто текст. Поэтому
все они прописаны с самого начала строки.

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

Если мы
посмотрим на содержимое какого-либо стандартного заголовочного файла, например,
того же stdio.h, то вначале
увидим такие строчки:

#ifndef _INC_STDIO
#define _INC_STDIO

А ниже
обязательно будет записана директива:

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

#include <stdio.h>
#include <stdio.h>

То программа
скомпилируется и отработает без ошибок, так как содержимое файла stdio.h было добавлено
макропроцессором в текущий модуль только один раз. Почему так произошло?
Смотрите. Когда файл stdio.h подключался
первый раз, то макроимя _INC_STDIO отсутствовало и
условие директивы #ifndef оказалось истинным, так как #ifndef – это аналог #if !defined. Раз условие
истинно, то все, что определено до директивы #endif включается в
файл, в том числе и директива «#define _INC_STDIO», которая
определяет макроимя _INC_STDIO. Теперь оно
существует в текущем модуле. Это значит, при повторном включении файла stdio.h условие
директивы #ifndef окажется ложным
и фрагмент дублироваться не будет.

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

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

Директива

Описание

#if

Проверка
произвольного условия.

#else

Определение
ветки «иначе».

#endif

Директива
(метка) завершения фрагмента для условия.

#ifdef

Сокращение
от if defined. Позволяет
делать проверку на наличие макроимени в текущем модуле.

#ifndef

Сокращение
от if !defined. Позволяет
делать проверку на отсутствие макроимени в текущем модуле.

#elif

Сокращение
от else if. Позволяет делать
проверку по ветке «иначе».

#elifdef

Сокращение от else if defined. Для реализации проверки наличия
макроимени по ветке «иначе».

#elifndef

Сокращение
от else if !defined. Для
реализации проверки отсутствия макроимени по ветке «иначе».

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

Видео по теме

Понравилась статья? Поделить с друзьями:
  • Инструкция которая говорит что определенные утверждения должны удовлетворяться в определенных точках
  • Инструкция как играть в мафию
  • Инструкция котла бакси экофор 24
  • Инструкция как приучить ребенка к горшку
  • Инструкция как завязать шнурки для детей