Препроцессор (англ. preprocessor ) – программа, выполняющая предварительную обработку входных данных для другой программы [19.1]. Препроцессор языка программирования С просматривает программу до компилятора и заменяет в программе определенные сочетания символов (символические аббревиатуры) на соответствующие директивы. Он отыскивает и подключает к программе необходимые файлы и может изменить условия компиляции [19.1]. Препроцессор имеет тот же смысл, что и буферный процессор.
Препроцессор языка С выполняет макроподстановку,
В предыдущих лабораторных работах уже встречались строки с начальным символом #. Это #include и #define. Первая директива (инструкция) использовалась для подключения заголовочных файлов, в первую очередь из библиотеки языка С, а вторая – для подстановки символов или чисел в определенные места программного кода.
Имеются следующие директивы препроцессора:
| #define | #endif | # |
#line |
| #elif | #error | #ifndef | # |
| #else | #if | #include | #undef |
Каждая директива препроцессора должна занимать отдельную строку. Например, строка
#include <stdio.h> #include <stdlib.h>
рассматривается как недопустимая [19.3].
Директива #define определяет идентификатор и последовательность символов, которая будет подставляться вместо идентификатора каждый раз, когда он встретится в исходном файле. Идентификатор называется именем макроса, а сам процесс замены – макрозаменой (макрорасширением, макрогенерацией, макроподстановкой) [19.3]. В общем виде директива #define выглядит следующим образом (должно быть задано буквами латинского алфавита):
#define имя_макроса последовательность_символов
В определении, как правило, в конце последовательности символов не ставится точки с запятой. Между идентификатором и последовательностью символов последовательность_символов может быть любое количество пробелов, но признаком конца последовательности символов может быть только разделитель строк [19.3].
Имена макросов обычно задаются с помощью букв верхнего регистра.
У директивы #define имя макроса может определяться с формальными параметрами. Тогда каждый раз, когда в программе встречается имя макроса, то используемые в его определении формальные параметры заменяются теми аргументами, которые встретились в программе. Такого рода макросы называются макросами с формальными параметрами (макроопределениями с параметрами и макросами, напоминающими функции ) [19.3]. Ключевым элементом макроса с параметрами являются круглые скобки, внутри которых находятся собственно формальные параметры. Рассмотрим пример макроса с тремя параметрами для определения следующего условия: будет ли остаток от деления случайной функции на правую границу интервала больше, чем половина этого интервала.
Программный код решения примера
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <time.h>
#define MAX(a,b,c) ((1+rand()%(b)) > ((a)+(b))/2 ) ? (c):(b)
int main (void) {
int a, b, c;
srand((unsigned) time(NULL));
printf("\n Enter a, b, c: ");
scanf_s("%d%d%d", a, b, c);
printf("\n MAX(a,b,c): %d\n", MAX(a,b,c));
printf("\n\n ... Press any key: ");
_getch();
return 0;
}
В общем случае рекомендуется заключать в круглые скобки формальные параметры, над которыми выполняются те или иные действия.
Использование вместо настоящих функций макросов с формальными параметрами (т. е. a, b, с ) дает следующее преимущество: увеличивается скорость выполнения кода, потому что в таких случаях не надо тратить ресурсы на вызов функций. Кроме того, макрос не чувствителен к типу данных, т. е. в нем отсутствует проверка типов аргументов. Однако если у макроса с формальными параметрами очень большие размеры, то тогда из-за дублирования кода увеличение скорости достигается за счет увеличения размеров программы [19.3]. И еще, в конструировании макроса следует быть очень внимательным. Как правило, макросы используются для небольших пользовательских функций [19.4].
Директива #error заставляет компилятор прекратить компиляцию [19.3]. Эта директива используется в основном для отладки. В общем виде директива #error выглядит следующим образом:
#error сообщение – об – ошибке
Заданное сообщение об ошибке ( сообщение – об – ошибке ) в двойные кавычки не заключается. Когда встречается данная директива, то выводится сообщение об ошибке – возможно, вместе с другой информацией, определяемой компилятором [19.3].
Директива #include дает указание компилятору читать еще один исходный файл – в дополнение к тому файлу, в котором находится сама эта директива [19.3]. Имя исходного файла (подключаемого файла) должно быть заключено в двойные кавычки или в угловые скобки. Обычно имена стандартных заголовочных файлов заключают в угловые скобки. А использование кавычек обычно приберегается для имен специальных файлов, относящихся к конкретной программе. Способ поиска файла зависит от того, заключено ли его имя в двойные кавычки или же в угловые скобки. Если имя заключено в угловые скобки, то поиск файла проводится тем способом, который определен в компиляторе. Часто это означает поиск определенного каталога, специально предназначенного для хранения таких файлов. Если имя заключено в кавычки, то поиск файла проводится другим способом. Во многих компиляторах это означает поиск файла в текущем рабочем каталоге. Если же файл не найден, то поиск повторяется уже так, как будто имя файла заключено в угловые скобки [19.3].
Файлы, имена которых находятся в директивах #include, могут в свою очередь содержать другие директивы #include. Они называются вложенными директивами #include. Количество допустимых уровней вложенности у разных компиляторов может быть разным. Однако в стандарте С89 предусмотрено, что компиляторы должны допускать не менее 8 таких уровней [19.3].
Директивы
Директива
#if константное_выражение последовательность операторов программного кода #endif
Если находящееся за директивой #if константное выражение истинно, то компилируется код, который находится между этим выражением и #endif, которая обозначает конец блока #if. Константное выражение может быть задано через директиву #define. При этом, если, например, задано число, не равное нулю, то такое константное выражение будет истинно. Если же заданное число есть нуль, то константное выражение будет ложным. В частности константное выражение может быть задано макросом с формальными параметрами, которые должны быть в свою очередь также константными параметрами.
Директива
#if константное_выражение последовательность операторов программного кода #else альтернативная последовательность операторов программного кода #endif
Аналогично используются директивы #elif (else if), которые в общем случае имеют следующий вид:
#if константное_выражение последовательность операторов программного кода #elif 2_ константное_выражение 2_ я_последовательность операторов программного кода #elif 3_ константное_выражение 3_ я_последовательность операторов программного кода . . . #elif N_ константное_выражение N_ я_последовательность операторов программного кода #else альтернативная последовательность операторов программного кода #endif
Если константное выражение в директиве #elif истинно (не нулевое, например), то будет компилироваться соответствующая последовательность операторов программного кода. При этом другие выражения в директивах #elif проверяться уже не будут, в том числе и директива #else.
Особенностью рассмотренных конструкций является то, что проверка выражений осуществляется внутри директив #if и #endif.
В соответствии со стандартом С89 у директив #if и #elif может быть не менее 8 уровней вложенности. При вложенности каждая директива #endif, #else или #elif относится к ближайшей директиве #if или #elif [19.3].
Каждая директива #if сопровождается директивой #endif.
Директива
#ifdef имя_макроса последовательность операторов #endif
Директива
Директива
#ifndef имя_макроса последовательность операторов #endif
Последовательность операторов будет компилироваться, если имя макроса еще не определено директивой #define. В директивах #
Согласно стандарту С89 допускается не менее 8 уровней #
Директива #undef удаляет заданное определение имени макроса, то есть "аннулирует" его определение; само имя макроса должно находиться после директивы [19.3].
В общем случае директива #undef выглядит следующим образом:
#undef имя_макроса
Директива #undef используется в основном для того, чтобы локализовать имена макросов в тех участках кода, где они нужны.
Для того чтобы узнать определено ли имя макроса, можно использовать директиву #if в сочетании с оператором времени компиляции defined [19.3].
defined имя_макроса
Если имя_макроса определено, то выражение считается истинным; в противном случае – ложным.
Единственная причина, по которой используется
Директива #line изменяет содержимое __LINE__ и __FILE__, которые являются зарезервированными идентификаторами (макросами) в компиляторе. В первом из них содержится номер компилируемой в данный момент строки программного кода программы [19.3]. А второй идентификатор – это строка, содержащая имя компилируемого исходного файла.
Директива #line выглядит следующим образом:
#line номер "имя_файла"
В определении директивы #line обязательным является номер строки, относительно которой будет выполняться подсчет следующих строк. Второй параметр "имя_файла" является не обязательным. Если его не будет, то идентификатор __FILE__ будет содержать путь и имя программы. Если указать в качестве параметра новое имя файла – "имя_файла", то __FILE__ будет содержать это новое имя файла.
Директива #line в основном используется для отладки и специальных применений [19.3].
Операторы # и ## применяются в сочетании с директивой #define [19.3].
Оператор #, который обычно называют оператором превращения в строку (stringize), превращает аргумент, перед которым стоит, в строку, заключенную в кавычки. Оператор # должен использоваться в макросах с аргументами, поскольку операнд после # ссылается на аргумент макроса [19.5].
Оператор ##, который называют оператором склеивания (pasting), или конкатенации конкатенирует две лексемы. Операция ## должна иметь два операнда [19.5].
Операторы # и ## предусмотрены для работы препроцессора в некоторых особых случаях [19.3,19.5].
Директива #
В языке С определены пять встроенных, предопределенных имен
| Предопределенные | |
|---|---|
| Символическая константа | Объяснение |
| __LINE__ | Возвращает целую константу для номера текущей обрабатываемой строки исходного кода программы |
| __FILE__ | По умолчанию возвращает в виде строки символов имя компилируемого исходного файла |
| __DATE__ | Возвращает в виде строки символов дату (мм дд гг) начала компиляции текущего исходного файла |
| __TIME__ | Возвращает в виде строки символов время (чч:мм:сс) начала компиляции текущего исходного файла |
| __STDC__ | Возвращает целую константу 1, которая указывает на то, что данная реализация совместима со стандартом ANSI |
Макрос assert, определенный в заголовочном файле assert.h, проверяет значение выражения [19.5]. Если значение выражения равно 0 (ложное значение), то assert распечатывает (например, выводит на консоль) сообщение об ошибке и вызывает функцию abort() (из библиотеки stdlib.h ), завершающую работу программы. Например, в программе переменная х должна принимать значения, не превышающие 10. Для проверки и подтверждения такого условия в программу можно включить следующую строку:
assert( x <= 10 );
Если при выполнении данного макроса переменная х окажется больше 10, то выдается сообщение, содержащее номер строки и имя файла (например, main.c), в котором нарушено условие, а выполнение программы при этом прерывается. Формат выводимого сообщения зависит от конкретной реализации системы программирования [19.3]. С помощью assert можно производить отладку программы во многих ее местах. Когда программа будет отлажена, то действие макроса assert можно устранить с помощью
#define NDEBUG
#define NDEBUG
Пример . Напишите программу с использованием макро-функции по определению числа, введенного пользователем, на предмет его простоты. Предусмотрите также вывод на консоль времени компиляции программы и сообщения о реализации языка С.
Программный код решения примера:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <conio.h>
// Макрос с формальными параметрами
#define SIMPLE(x, d, b) for(d = 2; d < x; d++) \
if (!(x%d)) b = 0; \
if (b) puts("\n It is the simple number"); \
else puts("\n It is not the simple number");
int main (void) {
int b = 1, d = 0, x = 13;
printf("\n Enter the natural number: ");
scanf("%d", x);
// Макрос с действительными параметрами
SIMPLE(x, d, b);
printf("\n %5sTime: %s\n Version C: %d \n", "", \
__TIME__, __STDC__);
printf("\n ... Press any key: ");
_getch();
return 0; }
В первой строке программы включена scanf() в среде MS Visual Studio 2008. В программе показано применение макроса функции с тремя формальными параметрами и несколькими строками программного кода.
Результат выполнения программы приведен на рис 19.1.
(рис 19.1) Проверка введенного числа на простотуРезультат Version C: 1 означает, что компилятор поддерживает стандарт ANSI С.
main(), для вывода соответствующего сообщения на консоль.swap() ).#define.__TIME__.Пример 2. Выполните проверку подключаемого тестового файла и вывести на консоль содержимого этого файла. Содержимое файла – стихотворный пример бесконечной рекурсии: у попа была собака ....
Программный код решения примера:
// Файл с главной функцией main()
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <locale.h>
// Подключение текстового файла
#ifndef AZA
#define AZA
#include "dog.txt"
#endif
int main (void) {
short i , j, n, in;
i = j = 0;
// для поддержки русских шрифтов
setlocale (LC_ALL, "rus");
printf("\n Введите количество стихотворных строф: ");
in = scanf("%hd", n);
if (in != 1 || n < 1) {
printf("\n Ошибка ввода данных. Нажмите любую клавишу: ");
_getch();
exit(1);
}
// Условие распечатки текстового файла
#ifdef AZA
puts("");
for (j = 0; j < n; j++ )
{
i = 0;
while (d[i] != NULL)
{
printf(" ");
puts(d[i]);
i++;
}
}
#endif
printf("\n ... Нажмите любую клавишу: ");
_getch();
return 0;
}
Содержимое текстового файла dog.txt:
char *d[] = {
"У попа была собака,", \
"Он её любил,", \
"Она съела кусок мяса,", \
"Он её убил...",\
"Вырыл ямку, закопал,", \
"На дощечке написал:\n"
};
Решение примера выполнено в виде двух файлового проекта. Инициализация переменных в главной функции сделано на случай, если не будет определена директива #define AZA, чтобы не было предупреждений компилятора о неиспользованных переменных i и j.
Пример выполнения программы показан на рис 19.2.
(рис 19.2) Пример распечатки текстового файлаЗадание 2
#ifdef примените другую директиву #else.AZA?Пример 3. С помощью директив _DEBUG напишите программу ввода слов с клавиатуры с проверкой возможности компиляции программного кода.
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
int main (void) {
char str[80];
// Начало проверки компилируемого кода
#ifdef _DEBUG
printf("\n Start debugging\n");
#endif
do {
printf("\n Enter a word or \"z\" to exit: ");
gets_s(str, 79);
#if _DEBUG
printf("\n The word is \"%s\"\n", str );
#else
#error This version is not to the C Run-Time Library. \
Break to debugging.
#endif
} while (str[0] != 'z' str[0] != 'Z');
printf("\n\n ... Press any key: ");
_getch();
return 0;
}
_DEBUG будет определяться (существовать) в режиме Debug, которое находится в списке главного меню интегрированной системы MS Visual Studio 2010.
На рис 19.3 показан выбор режима отладки Debug.
(рис 19.3) Выбор режима отладки DebugПример выполнения программы показан на рис 19.4.
(рис 19.4) Пример выполнения режима отладки программыЗадание 3
#error.Пример 4. С помощью директивы #define и оператора препроцессора # напишите программу определения кода вводимого символа.
Программный код решения примера:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <conio.h>
#include <locale.h>
#define CHAR_COD(c) ""#c""
int main (void) {
char ch;
setlocale (LC_ALL, "rus");
printf("\n Введите символ: ");
scanf("%c", ch);
printf("\n %s \"%c\", его код %d", CHAR_COD(Символ), ch, ch);
printf("\n\n ... Нажмите любую клавишу: ");
_getch();
return 0;
}
Оператор # должен использоваться в макросах с аргументами, поскольку операнд после ссылается на аргумент макроса [19.5]. В данном случае строка "Символ" подставляется в заменяющий текст вместо #c.
Пример выполнения программы показан на рис 19.5.
(рис 19.5) Пример определения кода вводимого символаЗадание 4
"z".printf(), где Х – номер компьютера, на котором выполняется лабораторная работа.Пример 5. С помощью директивы #define и оператора препроцессора ## классифицируйте четность или нечетность кода вводимого символа.
Программный код решения примера:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <conio.h>
#include <locale.h>
// LEXEME - лексема
#define TWO_LEXEME(a,b) a ## b
#define CHAR_COD(c) ""#c""
int main (void)
{
char ch;
setlocale (LC_ALL, "rus");
printf("\n Введите символ: ");
scanf("%c", ch);
if (!(ch % 2))
printf("\n %s \"%c\", его код %d %s", \
CHAR_COD(Символ), ch, ch, TWO_LEXEME(" - четный","!"));
else
printf("\n %s \"%c\", его код %d %s", \
CHAR_COD(Символ), ch, ch, TWO_LEXEME(" - не ", "четный."));
printf("\n\n ... Нажмите любую клавишу: ");
_getch();
return 0; }
Данная программа – некоторое расширение предыдущей программы. ## осуществляется с помощью макроса TWO_LEXEME() с двумя аргументами.
Пример выполнения программы показан на рис 19.6.
(рис 19.6) Пример классификации четности символаЗадание 5
str1 и str2.##, где Х – номер компьютера, на котором выполняется лабораторная работа.Пример 6. Напишите программу формирования структуры с информацией о студенте (об учащемся) и с помощью директивы # осуществить оптимизацию использования памяти, выделяемой под структуру.
Для решения примера используем справку системы Visual Studio 2010 (для этого из меню Help $$\to$$ Index $$\to$$ Look for: вписать
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
#pragma pack (show)
struct Student
{
char name[21]; // имя, фамилия
int age; // возраст, полных лет
char gender; // пол, мужской, женский
double mean; // средний балл успеваемости
} st1;
#pragma pack (push)
#pragma pack (1)
#pragma pack (show)
struct Student_pack
{
char name[21]; // имя, фамилия
int age; // возраст, полных лет
char gender; // пол, мужской, женский
double mean; // средний балл успеваемости
} st2;
#pragma pack (pop)
#pragma pack (show)
int main (void)
{
puts("");
printf (" sizeof (struct st1) = %d\n", sizeof (st1));
printf (" sizeof (struct st2) = %d\n", sizeof (st2));
printf("\n ... Press any key: ");
_getch();
return 0;
}
В программе опция pack(show) используется для диагностики текущей упаковки полей структуры. Опция pack(1) используется для выравнивания областей памяти полей структуры, кратных единице. Опция pack(push) используется для "вталкивания" параметров. Опция pack(pop) используется для "выталкивания" параметров.
После компиляции программы можно видеть сообщения, показанные на рис 19.7.
(рис 19.7) Сообщения об упаковке полей структуры Предупреждения (warning) уведомляют о размере упаковки. Сначала область памяти выделяется под размер в байтах, кратных 8, затем, кратных 1. После выталкивания параметров снова область памяти становится кратной 8 – наибольшему типу данных double элемента mean структуры.
Результат выполнения программы показан на рис 19.8.
(рис 19.8) Пример определения месяца годаКак видно, размер первой структуры равно 40 байтов (кратных 8). Размер второй структуры, для которой произведена упаковка, равен 34, что соответствует сумме размеров полей структуры, т. е. 34 = 21 + 4 + 1 + 8.
.
(рис 19.9) Настройка компилятора (Code Generation $$\to$$ Struct Alignment)
Задание 6
pack() примите 1, 2, 4, 8, 16. Объясните результаты выполнения программы.char примите равным 31, т. е. char name[31]. Объясните результат выполнения программы.float, short int. Объясните результат выполнения программы.Препроцессор (англ. preprocessor ) – программа, выполняющая предварительную обработку входных данных для другой программы [19.1]. Препроцессор языка программирования С просматривает программу до компилятора и заменяет в программе определенные сочетания символов (символические аббревиатуры) на соответствующие директивы. Он отыскивает и подключает к программе необходимые файлы и может изменить условия компиляции [19.1]. Препроцессор имеет тот же смысл, что и буферный процессор.
Препроцессор языка С выполняет макроподстановку,
В предыдущих лабораторных работах уже встречались строки с начальным символом #. Это #include и #define. Первая директива (инструкция) использовалась для подключения заголовочных файлов, в первую очередь из библиотеки языка С, а вторая – для подстановки символов или чисел в определенные места программного кода.
Имеются следующие директивы препроцессора:
| #define | #endif | # |
#line |
| #elif | #error | #ifndef | # |
| #else | #if | #include | #undef |
Каждая директива препроцессора должна занимать отдельную строку. Например, строка
#include <stdio.h> #include <stdlib.h>
рассматривается как недопустимая [19.3].
Директива #define определяет идентификатор и последовательность символов, которая будет подставляться вместо идентификатора каждый раз, когда он встретится в исходном файле. Идентификатор называется именем макроса, а сам процесс замены – макрозаменой (макрорасширением, макрогенерацией, макроподстановкой) [19.3]. В общем виде директива #define выглядит следующим образом (должно быть задано буквами латинского алфавита):
#define имя_макроса последовательность_символов
В определении, как правило, в конце последовательности символов не ставится точки с запятой. Между идентификатором и последовательностью символов последовательность_символов может быть любое количество пробелов, но признаком конца последовательности символов может быть только разделитель строк [19.3].
Имена макросов обычно задаются с помощью букв верхнего регистра.
У директивы #define имя макроса может определяться с формальными параметрами. Тогда каждый раз, когда в программе встречается имя макроса, то используемые в его определении формальные параметры заменяются теми аргументами, которые встретились в программе. Такого рода макросы называются макросами с формальными параметрами (макроопределениями с параметрами и макросами, напоминающими функции ) [19.3]. Ключевым элементом макроса с параметрами являются круглые скобки, внутри которых находятся собственно формальные параметры. Рассмотрим пример макроса с тремя параметрами для определения следующего условия: будет ли остаток от деления случайной функции на правую границу интервала больше, чем половина этого интервала.
Программный код решения примера
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <time.h>
#define MAX(a,b,c) ((1+rand()%(b)) > ((a)+(b))/2 ) ? (c):(b)
int main (void) {
int a, b, c;
srand((unsigned) time(NULL));
printf("\n Enter a, b, c: ");
scanf_s("%d%d%d", a, b, c);
printf("\n MAX(a,b,c): %d\n", MAX(a,b,c));
printf("\n\n ... Press any key: ");
_getch();
return 0;
}
В общем случае рекомендуется заключать в круглые скобки формальные параметры, над которыми выполняются те или иные действия.
Использование вместо настоящих функций макросов с формальными параметрами (т. е. a, b, с ) дает следующее преимущество: увеличивается скорость выполнения кода, потому что в таких случаях не надо тратить ресурсы на вызов функций. Кроме того, макрос не чувствителен к типу данных, т. е. в нем отсутствует проверка типов аргументов. Однако если у макроса с формальными параметрами очень большие размеры, то тогда из-за дублирования кода увеличение скорости достигается за счет увеличения размеров программы [19.3]. И еще, в конструировании макроса следует быть очень внимательным. Как правило, макросы используются для небольших пользовательских функций [19.4].
Директива #error заставляет компилятор прекратить компиляцию [19.3]. Эта директива используется в основном для отладки. В общем виде директива #error выглядит следующим образом:
#error сообщение – об – ошибке
Заданное сообщение об ошибке ( сообщение – об – ошибке ) в двойные кавычки не заключается. Когда встречается данная директива, то выводится сообщение об ошибке – возможно, вместе с другой информацией, определяемой компилятором [19.3].
Директива #include дает указание компилятору читать еще один исходный файл – в дополнение к тому файлу, в котором находится сама эта директива [19.3]. Имя исходного файла (подключаемого файла) должно быть заключено в двойные кавычки или в угловые скобки. Обычно имена стандартных заголовочных файлов заключают в угловые скобки. А использование кавычек обычно приберегается для имен специальных файлов, относящихся к конкретной программе. Способ поиска файла зависит от того, заключено ли его имя в двойные кавычки или же в угловые скобки. Если имя заключено в угловые скобки, то поиск файла проводится тем способом, который определен в компиляторе. Часто это означает поиск определенного каталога, специально предназначенного для хранения таких файлов. Если имя заключено в кавычки, то поиск файла проводится другим способом. Во многих компиляторах это означает поиск файла в текущем рабочем каталоге. Если же файл не найден, то поиск повторяется уже так, как будто имя файла заключено в угловые скобки [19.3].
Файлы, имена которых находятся в директивах #include, могут в свою очередь содержать другие директивы #include. Они называются вложенными директивами #include. Количество допустимых уровней вложенности у разных компиляторов может быть разным. Однако в стандарте С89 предусмотрено, что компиляторы должны допускать не менее 8 таких уровней [19.3].
Директивы
Директива
#if константное_выражение последовательность операторов программного кода #endif
Если находящееся за директивой #if константное выражение истинно, то компилируется код, который находится между этим выражением и #endif, которая обозначает конец блока #if. Константное выражение может быть задано через директиву #define. При этом, если, например, задано число, не равное нулю, то такое константное выражение будет истинно. Если же заданное число есть нуль, то константное выражение будет ложным. В частности константное выражение может быть задано макросом с формальными параметрами, которые должны быть в свою очередь также константными параметрами.
Директива
#if константное_выражение последовательность операторов программного кода #else альтернативная последовательность операторов программного кода #endif
Аналогично используются директивы #elif (else if), которые в общем случае имеют следующий вид:
#if константное_выражение последовательность операторов программного кода #elif 2_ константное_выражение 2_ я_последовательность операторов программного кода #elif 3_ константное_выражение 3_ я_последовательность операторов программного кода . . . #elif N_ константное_выражение N_ я_последовательность операторов программного кода #else альтернативная последовательность операторов программного кода #endif
Если константное выражение в директиве #elif истинно (не нулевое, например), то будет компилироваться соответствующая последовательность операторов программного кода. При этом другие выражения в директивах #elif проверяться уже не будут, в том числе и директива #else.
Особенностью рассмотренных конструкций является то, что проверка выражений осуществляется внутри директив #if и #endif.
В соответствии со стандартом С89 у директив #if и #elif может быть не менее 8 уровней вложенности. При вложенности каждая директива #endif, #else или #elif относится к ближайшей директиве #if или #elif [19.3].
Каждая директива #if сопровождается директивой #endif.
Директива
#ifdef имя_макроса последовательность операторов #endif
Директива
Директива
#ifndef имя_макроса последовательность операторов #endif
Последовательность операторов будет компилироваться, если имя макроса еще не определено директивой #define. В директивах #
Согласно стандарту С89 допускается не менее 8 уровней #
Директива #undef удаляет заданное определение имени макроса, то есть "аннулирует" его определение; само имя макроса должно находиться после директивы [19.3].
В общем случае директива #undef выглядит следующим образом:
#undef имя_макроса
Директива #undef используется в основном для того, чтобы локализовать имена макросов в тех участках кода, где они нужны.
Для того чтобы узнать определено ли имя макроса, можно использовать директиву #if в сочетании с оператором времени компиляции defined [19.3].
defined имя_макроса
Если имя_макроса определено, то выражение считается истинным; в противном случае – ложным.
Единственная причина, по которой используется
Директива #line изменяет содержимое __LINE__ и __FILE__, которые являются зарезервированными идентификаторами (макросами) в компиляторе. В первом из них содержится номер компилируемой в данный момент строки программного кода программы [19.3]. А второй идентификатор – это строка, содержащая имя компилируемого исходного файла.
Директива #line выглядит следующим образом:
#line номер "имя_файла"
В определении директивы #line обязательным является номер строки, относительно которой будет выполняться подсчет следующих строк. Второй параметр "имя_файла" является не обязательным. Если его не будет, то идентификатор __FILE__ будет содержать путь и имя программы. Если указать в качестве параметра новое имя файла – "имя_файла", то __FILE__ будет содержать это новое имя файла.
Директива #line в основном используется для отладки и специальных применений [19.3].
Операторы # и ## применяются в сочетании с директивой #define [19.3].
Оператор #, который обычно называют оператором превращения в строку (stringize), превращает аргумент, перед которым стоит, в строку, заключенную в кавычки. Оператор # должен использоваться в макросах с аргументами, поскольку операнд после # ссылается на аргумент макроса [19.5].
Оператор ##, который называют оператором склеивания (pasting), или конкатенации конкатенирует две лексемы. Операция ## должна иметь два операнда [19.5].
Операторы # и ## предусмотрены для работы препроцессора в некоторых особых случаях [19.3,19.5].
Директива #
В языке С определены пять встроенных, предопределенных имен
| Предопределенные | |
|---|---|
| Символическая константа | Объяснение |
| __LINE__ | Возвращает целую константу для номера текущей обрабатываемой строки исходного кода программы |
| __FILE__ | По умолчанию возвращает в виде строки символов имя компилируемого исходного файла |
| __DATE__ | Возвращает в виде строки символов дату (мм дд гг) начала компиляции текущего исходного файла |
| __TIME__ | Возвращает в виде строки символов время (чч:мм:сс) начала компиляции текущего исходного файла |
| __STDC__ | Возвращает целую константу 1, которая указывает на то, что данная реализация совместима со стандартом ANSI |
Макрос assert, определенный в заголовочном файле assert.h, проверяет значение выражения [19.5]. Если значение выражения равно 0 (ложное значение), то assert распечатывает (например, выводит на консоль) сообщение об ошибке и вызывает функцию abort() (из библиотеки stdlib.h ), завершающую работу программы. Например, в программе переменная х должна принимать значения, не превышающие 10. Для проверки и подтверждения такого условия в программу можно включить следующую строку:
assert( x <= 10 );
Если при выполнении данного макроса переменная х окажется больше 10, то выдается сообщение, содержащее номер строки и имя файла (например, main.c), в котором нарушено условие, а выполнение программы при этом прерывается. Формат выводимого сообщения зависит от конкретной реализации системы программирования [19.3]. С помощью assert можно производить отладку программы во многих ее местах. Когда программа будет отлажена, то действие макроса assert можно устранить с помощью
#define NDEBUG
#define NDEBUG
Пример . Напишите программу с использованием макро-функции по определению числа, введенного пользователем, на предмет его простоты. Предусмотрите также вывод на консоль времени компиляции программы и сообщения о реализации языка С.
Программный код решения примера:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <conio.h>
// Макрос с формальными параметрами
#define SIMPLE(x, d, b) for(d = 2; d < x; d++) \
if (!(x%d)) b = 0; \
if (b) puts("\n It is the simple number"); \
else puts("\n It is not the simple number");
int main (void) {
int b = 1, d = 0, x = 13;
printf("\n Enter the natural number: ");
scanf("%d", x);
// Макрос с действительными параметрами
SIMPLE(x, d, b);
printf("\n %5sTime: %s\n Version C: %d \n", "", \
__TIME__, __STDC__);
printf("\n ... Press any key: ");
_getch();
return 0; }
В первой строке программы включена scanf() в среде MS Visual Studio 2008. В программе показано применение макроса функции с тремя формальными параметрами и несколькими строками программного кода.
Результат выполнения программы приведен на рис 19.1.
(рис 19.1) Проверка введенного числа на простотуРезультат Version C: 1 означает, что компилятор поддерживает стандарт ANSI С.
main(), для вывода соответствующего сообщения на консоль.swap() ).#define.__TIME__.Пример 2. Выполните проверку подключаемого тестового файла и вывести на консоль содержимого этого файла. Содержимое файла – стихотворный пример бесконечной рекурсии: у попа была собака ....
Программный код решения примера:
// Файл с главной функцией main()
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <locale.h>
// Подключение текстового файла
#ifndef AZA
#define AZA
#include "dog.txt"
#endif
int main (void) {
short i , j, n, in;
i = j = 0;
// для поддержки русских шрифтов
setlocale (LC_ALL, "rus");
printf("\n Введите количество стихотворных строф: ");
in = scanf("%hd", n);
if (in != 1 || n < 1) {
printf("\n Ошибка ввода данных. Нажмите любую клавишу: ");
_getch();
exit(1);
}
// Условие распечатки текстового файла
#ifdef AZA
puts("");
for (j = 0; j < n; j++ )
{
i = 0;
while (d[i] != NULL)
{
printf(" ");
puts(d[i]);
i++;
}
}
#endif
printf("\n ... Нажмите любую клавишу: ");
_getch();
return 0;
}
Содержимое текстового файла dog.txt:
char *d[] = {
"У попа была собака,", \
"Он её любил,", \
"Она съела кусок мяса,", \
"Он её убил...",\
"Вырыл ямку, закопал,", \
"На дощечке написал:\n"
};
Решение примера выполнено в виде двух файлового проекта. Инициализация переменных в главной функции сделано на случай, если не будет определена директива #define AZA, чтобы не было предупреждений компилятора о неиспользованных переменных i и j.
Пример выполнения программы показан на рис 19.2.
(рис 19.2) Пример распечатки текстового файлаЗадание 2
#ifdef примените другую директиву #else.AZA?Пример 3. С помощью директив _DEBUG напишите программу ввода слов с клавиатуры с проверкой возможности компиляции программного кода.
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
int main (void) {
char str[80];
// Начало проверки компилируемого кода
#ifdef _DEBUG
printf("\n Start debugging\n");
#endif
do {
printf("\n Enter a word or \"z\" to exit: ");
gets_s(str, 79);
#if _DEBUG
printf("\n The word is \"%s\"\n", str );
#else
#error This version is not to the C Run-Time Library. \
Break to debugging.
#endif
} while (str[0] != 'z' str[0] != 'Z');
printf("\n\n ... Press any key: ");
_getch();
return 0;
}
_DEBUG будет определяться (существовать) в режиме Debug, которое находится в списке главного меню интегрированной системы MS Visual Studio 2010.
На рис 19.3 показан выбор режима отладки Debug.
(рис 19.3) Выбор режима отладки DebugПример выполнения программы показан на рис 19.4.
(рис 19.4) Пример выполнения режима отладки программыЗадание 3
#error.Пример 4. С помощью директивы #define и оператора препроцессора # напишите программу определения кода вводимого символа.
Программный код решения примера:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <conio.h>
#include <locale.h>
#define CHAR_COD(c) ""#c""
int main (void) {
char ch;
setlocale (LC_ALL, "rus");
printf("\n Введите символ: ");
scanf("%c", ch);
printf("\n %s \"%c\", его код %d", CHAR_COD(Символ), ch, ch);
printf("\n\n ... Нажмите любую клавишу: ");
_getch();
return 0;
}
Оператор # должен использоваться в макросах с аргументами, поскольку операнд после ссылается на аргумент макроса [19.5]. В данном случае строка "Символ" подставляется в заменяющий текст вместо #c.
Пример выполнения программы показан на рис 19.5.
(рис 19.5) Пример определения кода вводимого символаЗадание 4
"z".printf(), где Х – номер компьютера, на котором выполняется лабораторная работа.Пример 5. С помощью директивы #define и оператора препроцессора ## классифицируйте четность или нечетность кода вводимого символа.
Программный код решения примера:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <conio.h>
#include <locale.h>
// LEXEME - лексема
#define TWO_LEXEME(a,b) a ## b
#define CHAR_COD(c) ""#c""
int main (void)
{
char ch;
setlocale (LC_ALL, "rus");
printf("\n Введите символ: ");
scanf("%c", ch);
if (!(ch % 2))
printf("\n %s \"%c\", его код %d %s", \
CHAR_COD(Символ), ch, ch, TWO_LEXEME(" - четный","!"));
else
printf("\n %s \"%c\", его код %d %s", \
CHAR_COD(Символ), ch, ch, TWO_LEXEME(" - не ", "четный."));
printf("\n\n ... Нажмите любую клавишу: ");
_getch();
return 0; }
Данная программа – некоторое расширение предыдущей программы. ## осуществляется с помощью макроса TWO_LEXEME() с двумя аргументами.
Пример выполнения программы показан на рис 19.6.
(рис 19.6) Пример классификации четности символаЗадание 5
str1 и str2.##, где Х – номер компьютера, на котором выполняется лабораторная работа.Пример 6. Напишите программу формирования структуры с информацией о студенте (об учащемся) и с помощью директивы # осуществить оптимизацию использования памяти, выделяемой под структуру.
Для решения примера используем справку системы Visual Studio 2010 (для этого из меню Help $$\to$$ Index $$\to$$ Look for: вписать
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
#pragma pack (show)
struct Student
{
char name[21]; // имя, фамилия
int age; // возраст, полных лет
char gender; // пол, мужской, женский
double mean; // средний балл успеваемости
} st1;
#pragma pack (push)
#pragma pack (1)
#pragma pack (show)
struct Student_pack
{
char name[21]; // имя, фамилия
int age; // возраст, полных лет
char gender; // пол, мужской, женский
double mean; // средний балл успеваемости
} st2;
#pragma pack (pop)
#pragma pack (show)
int main (void)
{
puts("");
printf (" sizeof (struct st1) = %d\n", sizeof (st1));
printf (" sizeof (struct st2) = %d\n", sizeof (st2));
printf("\n ... Press any key: ");
_getch();
return 0;
}
В программе опция pack(show) используется для диагностики текущей упаковки полей структуры. Опция pack(1) используется для выравнивания областей памяти полей структуры, кратных единице. Опция pack(push) используется для "вталкивания" параметров. Опция pack(pop) используется для "выталкивания" параметров.
После компиляции программы можно видеть сообщения, показанные на рис 19.7.
(рис 19.7) Сообщения об упаковке полей структуры Предупреждения (warning) уведомляют о размере упаковки. Сначала область памяти выделяется под размер в байтах, кратных 8, затем, кратных 1. После выталкивания параметров снова область памяти становится кратной 8 – наибольшему типу данных double элемента mean структуры.
Результат выполнения программы показан на рис 19.8.
(рис 19.8) Пример определения месяца годаКак видно, размер первой структуры равно 40 байтов (кратных 8). Размер второй структуры, для которой произведена упаковка, равен 34, что соответствует сумме размеров полей структуры, т. е. 34 = 21 + 4 + 1 + 8.
.
(рис 19.9) Настройка компилятора (Code Generation $$\to$$ Struct Alignment)
Задание 6
pack() примите 1, 2, 4, 8, 16. Объясните результаты выполнения программы.char примите равным 31, т. е. char name[31]. Объясните результат выполнения программы.float, short int. Объясните результат выполнения программы.Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.