Правила программирования на Си и Си++

       

Если все альтернативы отпали, то используйте препроцессор


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

#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
#   efine 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
(вариант по умолчанию).

Содержание раздела