Правила программирования на С и С++. Главы 1-6 - Если все альтернативы отпали, то используйте препроцессор
ОГЛАВЛЕНИЕ
84. Если все альтернативы отпали, то используйте препроцессор.
Мы увидим в главе, посвященной С++, что препроцессор С не играет большой роли в С++. Хотя есть немного мест, где он все еще кстати. Вот первое из них:
#ifdef DEBUG# define D(x) x
#else
# define D(X) /* пусто */
#endif
Вместо макроса D() подставляется его аргумент, если вы занимаетесь отладкой, иначе он расширяется до пустой строки. Он используется так: f(){
D( printf("Это отладочная информация\n"); )}В данном случае аргументом D() является целиком оператор printf(), который исчезает после того, как вы закончите отладку.Другой подобный вариант использования кстати, когда вы должны инициализировать те несколько неизбежных глобальных переменных в большой программе. Проблема заключается в синхронизации объявлений переменных (в заголовочном файле) с их определениями (в файле .c), где реально выделяется память и переменные инициализируются. Вот образец заголовочного файла:
#ifdef ALLOC# define I(x) x
# define EXPORTED /* пусто */
#else
# define I(x) /* пусто */
# define EXPORTED extern
#endif
EXPORTED int glob_x[10] I( ={1, 2, 3, 4} );
EXPORTED some_object glob_y I( ("конструктор", "аргументы") );
В определенном месте своей программы (я обычно делаю это в файле с именем globals.cpp) вы помещаете следующие строки: #define ALLOC#include "globals.h"
Далее везде вы просто включаете этот файл без предварительной директивы #define ALLOC. Когда вы компилируете globals.cpp, директива #define ALLOC вызывает следующую подстановку: /* пусто */ int glob_x[10] ={1, 2, 3, 4} ;/* пусто */ some_object glob_y ("конструктор", "аргументы") );
Отсутствие #define ALLOC везде приводит к следующей подстановке: extern int glob_x[10] /* пусто */ ;extern some_object glob_y /* пусто */ ;
Последним примером использования препроцессора будет макрос ASSERT(), который выводит сообщение об ошибке и завершает программу, лишь если вы осуществляете отладку (директивой #define определена константа DEBUG) и аргумент ASSERT() имеет значение "ложь". Он очень удобен для тестирования, например, аргументов типа указателей со значением NULL. Вариант ASSERT(), используемый в виде: f( char *p){
ASSERT( p, "f() : Неожиданный аргумент NULL." );}определяется следующим образом: #ifdef DEBUG#define ASSERT(условие, сообщение)
if ( !(условие) ) \{\
fprintf(stderr, "ASSERT(" #условие ") НЕ ВЫПОЛНЕНО "\ "[Файл " __FILE__ ", Строка %d]:\n\t%s\n",\__LINE__, (сообщение) );\
exit( -1 );\}\else
#else# define ASSERT(c,m) /* пусто */
#endif
В вышеуказанном примере ASSERT() выводит следующую строку при отрицательном результате проверки: ASSERT(p) НЕ ВЫПОЛНЕНО [Файл whatever.cpp, Строка 123]:f() : Неожиданный аргумент NULL.
и затем выходит в вызывающую программу. Он получает текущее имя файла и номер строки от препроцессора, используя предопределенные макросы __FILE__ и __LINE__. Условие, вызывающее отрицательный результат, выводится посредством оператора получения строки ANSI С (символ #), который фактически окружает расширенный аргумент кавычками после выполнения подстановки аргумента. Строка #условие расширяется до "p" в настоящем примере). Затем вступает в действие обычная конкатенация строк С для слияния вместе разных строк, создавая единый отформатированный аргумент для fprintf().Вы должны использовать здесь препроцессор, потому что вам нужно вывести на консоль имя файла и номер строки, для которых выполнена проверка. Встроенная функция С++ может вывести лишь имя того файла с номером строки, в котором определена встроенная функция.
Все компиляторы, поддерживающие стандарт ANSI С, должны реализовывать макрос assert(expr) в заголовочном файле assert.h, но макрос ANSI С не может выводить заказанное сообщение об ошибке. Макрос ANSI С assert() действует, если не определена константа NDEBUG (вариант по умолчанию).