Директивы препроцессора.
лекция № 3 - Директивы препроцессора. Часть 1.
лекция № 3 - Директивы препроцессора. Часть 2.
Директивы препроцессора играют важную роль для языков программирования в которых есть препроцессор :-)
Директивы начинаются с символа #.
Смотрим ниже директивы препроцессора в языке Си.
#if
#ifdef
#ifndef
#elif#else
#endif
#include
#define
#undef
#line
#error
#pragma
Начнем с директивы #include, которая включает в текст программы содержимое указанного файла:
файл hello.h
int a;
файл main.c
#include "hello.h" int main() { return 0; }
После обработки пропроцессора, компилятору уйдет следующий код
int a; int main() { return 0; }
Как вы видите, препроцессор вставил содержимое файла hello.h в тот файл в котором было указано это сделать, в файл main.c
Проверить какой текст программы уйдет компилятору, можно с помощью gcc или cpp(эта программа по идее идет вместе с компилятором), нужно выполнить в терминале или консоле:
user@user:~/tmp/c_lng$ gcc -E main.c
или
user@user:~/tmp/c_lng$ cpp main.c
на экране мы увидим
# 1 "main.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "main.c"
# 1 "hello.h" 1
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "main.c"
# 1 "hello.h" 1
int a;
# 2 "main.c" 2
int main()
{
return 0;
}
# 2 "main.c" 2
int main()
{
return 0;
}
результаты можно перенаправить в файл:
user@user:~/tmp/c_lng$ gcc -E main.c > mainafterpreproc.c
или
user@user:~/tmp/c_lng$ cpp main.c > mainafterpreproc.c
Будет создан файл с именем mainafterpreproc.c в котором будет интересующая нас информация.
Если имя файла обрамлено треугольными скобками <a_file.h>, то поиск этого файла происходит по стандартным путям для операционной системы, по которым располагаются различные библиотеки.
Если имя файла обрамлено кавычками "a_file.h", то поиск этого начинается с пути по которому находится собственно говоря исходный код, то есть в локальной директории, если же там этого файла нет, значит поиск будет по стандартным путям.
Введите себе правило, если это стандартная библиотка то <>, если это ваша библиотка то "".
Стандартная - <stdio.h>
Ваша - "hello.h"
Можно включать любые файлы с любым расширением, самое главное чтобы содержимое файла соответствовало правилам языка.
Но есть принятые правила, .h заголовочные файлы, значит их и включаем.
Это не мешает вам создать файлы с константами, назвать его superhide.mp4 и включить его в свою программу :-) . Я не рекомендую так делать, потому что с такого начинается бардак.
С #include разобрались.
#define - определяет макрос или символьную константу.
#undef - отменяет ранее объявленный макрос или константу
#include <stdio.h> #define DEBUG #define HELLO_STR "hello!" #define MIN_AGE 18 #define MAX_AGE 50 int main() { printf("%s\n", HELLO_STR); printf("%i - %i\n", MIN_AGE, MAX_AGE); #undef HELLO_STR printf("%s\n", HELLO_STR); // compilation error return 0; }
Чтобы данный код собрался, закомментируйте строку с комментарием
// compilation error
#define DEBUG - объявляет символьную константу без значения, просто вводит слово DEBUG так сказать в программу, и в коде программы, используя #if мы можем проверять, есть ли DEBUG, если есть то можем выводить отладочные сообщения на экран, если же нету(просто закомментировать объявление), то отладочные сообщения не нужны. Об этом ниже.
#define HELLO_STR "hello!" - объявляет строковую константу имя которой HELLO_STR а значение "hello!".
#define MIN_AGE 18
#define MAX_AGE 50 - аналогично объявляет целочисленные константы, для минимального возраста и максимального.
Препроцессор когда видит в коде использование таких констант, просто вместо них подставляет их значения. Вы можете в этом убедиться когда посмотрите на результат работы препроцессора, об этом было выше.
#undef HELLO_STR - отменяет объявленную ранее константу HELLO_STR, поэтому и ошибка.
Поэксперементируйте с этим кодом.
#if - проверка истинности какого-либо условия, если выражение истинно(объявлена ли константа или нет), код включаетсяв сборку. Зачастую проверяют объявлены ли константы или нет с помощью слова defined( an expression ).
#include <stdio.h> #define DEBUG int main() { #if defined(DEBUG) printf("%s:%i:%s - debug message\n", __FILE__, __LINE__, __FUNCTION__); #endif return 0; }В этом коде мы вводим константу DEBUG у которой нет значения, просто строковый литерал, если так можно выразиться. И с помощью директивы
#if defined(DEBUG) - проверяем, версия программы для отладки? Если да, то выводим отладочное сообщение.
Константы __FILE__, __LINE__, __FUNCTION__ означают что во время предкомпиляции(во время обработки кода препроцессором), вместо этих констант будут подставлены - имя файла, номер линии, имя функции.
Эти константы поддрерживаются всеми популярными компиляторами.
Вы могли уже видеть в заголовочных файлах следующую конструкцию:
#ifndef _FILE_H_
#define _FILE_H_
...
#endif
Это защита от повторного включения файла. Если перевести на человеческий язык, то получится следующее -
если нет объявления _FILE_H_
объявить _FILE_H_
...
блок if/ifndef/ifdef закончен.
файл main.c
#include "hello.h" #include "hello.h" int main() { return 0; }
файл hello.h
int a = 0;
При попытке собрать данный код, будет ошибка сборки, так как содержимое файла hello.h будет включено дважды. Значит будет попытка повторно объявить и инициализировать переменную a. Попробуйте.
Но! если мы, изменим наш hello.h следующим образом:
#ifndef HELLO_H #define HELLO_H int a = 0; #endif // HELLO_H
То все будет в порядке, так как при повторной попытке включения, будет проверка #ifndef HELLO_H - что означает, не было ли объявления HELLO_H, что на самом деле было при первом включении, значит код между #ifndef и #endif повторно включен не будет.
Также с помощью директив, если ваш код имеет заявку на кроссплатформенность(одного языка мало, обычно для программного обеспечения нужны еще и сторонние библиотеки), можно проверять под какой платформой, или каким компилятором код собирается, и включать нужные для этой платформы заголовочные файлы.
Макросы по которым можно определить платформу, можно посмотреть тут или тут, макросы для определения компилятора тут, но это один из вариантов, поищите в интернетах больше информации по этому поводу.
Теперь посмотрите на код ниже, запустите его у себя, подкорректируйте его, если нужно. Думаю разобраться с кодом труда не составит.
#include <stdio.h> int main() { #if defined(__GNUC__) printf("GNU Compiler detected!\n"); #if defined(__gnu_linux__) printf("Linux OS detected!\n"); #elif defined(__WIN32__) || defined(__WIN64__) printf("Windows OS detected!\n"); #else printf("Unknown OS detected!\n"); #endif #endif #if defined(__MINGW32__) || defined(__MINGW64__) printf("MINGW Compiler detected!\n"); #endif #if defined(_MSC_VER) printf("Microsoft Compiler detected!\n"); #endif return 0; }
Директива #line - указание имени файла и номера текущей строки для компилятора, в нормальной жизни программиста обычно не используется
:-)
#error - вывод сообщения в консоль сборки и остановка сборки
int main() { #if !defined(__GNUC__) #error "only GNU Compiler" #endif return 0; }
В этом коде мы проверяем, если макрос __GNUC__ отсутствует, то мы выводим сообщение о том что нужен только GNU компилятор. Соответственно сборка останавливается, и в консоль выводится наше сообщение.
#warning - вывод сообщения без остановки сборки, в основном для вывода информационных сообщений в консоль сборки
int main() { #warning "Need refactoring" #warning "This version not tested" #if !defined(__GNUC__) #warrning "tested only on GNU Compiler" #endif return 0; }
#pragma - указание действия, зависящего от реализации, для препроцессора или компилятора. У каждого компилятора может быть свой набор действий. Нужно читать справку к компилятору.
К примеру у компилятора в поставке с Microsoft Visual Studio есть возможность отключать для определенных файлов, предупреждения компилятора:
#pragma warning (disable:4700) - отключает вывод предупреждения
компилятора(с номером 4700) в консоль сборки, для того файла в начале
которого указана эта инструкция.
у Microsof для запрета повторной сборки была такая инструкция
#pragma once
вместо православного
#ifndef _H_
#define _H_
#endif
Позднее компиляторы GCC стали тоже поддерживать эту инструкцию, поэтому в начале заголовочных файлов можно также писать
#pragma once
Спасибо за внимание!
Комментарии
Отправить комментарий