Поиск…


Вступление

Все команды препроцессора начинаются с символа хеша (фунта) # . Макрос AC - это только команда препроцессора, которая определяется с помощью #define препроцессора #define . На этапе предварительной обработки препроцессор C (часть компилятора C) просто заменяет тело макроса везде, где появляется его имя.

замечания

Когда компилятор встречает макрос в коде, он выполняет простую замену строк, никаких дополнительных операций не выполняется. Из-за этого изменения препроцессора не учитывают область программ C - например, определение макроса не ограничено тем, что оно находится внутри блока, поэтому на него не влияет '}' который завершает оператор блока.

Препроцессор, по замыслу, не завершается полным - существует несколько типов вычислений, которые не могут быть выполнены только препроцессором.

Обычно у компиляторов есть флаг командной строки (или параметр конфигурации), который позволяет нам прекратить компиляцию после фазы предварительной обработки и проверить результат. На платформах POSIX этот флаг равен -E . Таким образом, запуск gcc с этим флагом печатает расширенный источник в stdout:

$ gcc -E cprog.c

Часто препроцессор реализуется как отдельная программа, которая вызывается компилятором, общее имя для этой программы - cpp . Ряд препроцессоров выделяет вспомогательную информацию, такую ​​как информация о номерах строк, которая используется для последующих этапов компиляции для генерации отладочной информации. В случае, когда препроцессор основан на gcc, опция -P подавляет такую ​​информацию.

$ cpp -P cprog.c

Условное включение и модификация подписи условных функций

Чтобы условно включить блок кода, препроцессор имеет несколько директив (например, #if , #ifdef , #else , #endif и т. Д.).

/* Defines a conditional `printf` macro, which only prints if `DEBUG`
 * has been defined
 */
#ifdef DEBUG
#define DLOG(x) (printf(x))
#else
#define DLOG(x)
#endif

Нормальные операторы отношения C могут использоваться для условия #if

#if __STDC_VERSION__ >= 201112L
/* Do stuff for C11 or higher */
#elif __STDC_VERSION__ >= 199901L
/* Do stuff for C99 */
#else
/* Do stuff for pre C99 */
#endif

Директивы #if ведут себя аналогично выражению C if , он должен содержать только интегральные константные выражения и не выполнять никаких бросков. Он поддерживает один дополнительный унарный оператор, defined( identifier ) , который возвращает 1 если идентификатор определен, и 0 противном случае.

#if defined(DEBUG) && !defined(QUIET)
#define DLOG(x) (printf(x))
#else
#define DLOG(x)
#endif

Модификация подписи условной функции

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

Например, предположим, что существует некоторая функция SHORT SerOpPluAllRead(PLUIF *pPif, USHORT usLockHnd) которая при выполнении тестовой сборки хочет создать журнал об использовании. Однако эта функция используется во многих местах, и желательно, чтобы при генерации журнала часть информации заключалась в том, чтобы знать, откуда вызывается функция.

Поэтому, используя условную компиляцию, вы можете иметь что-то вроде следующего в include-файле, объявляющем функцию. Это заменяет стандартную версию функции отладочной версией функции. Препроцессор используется для замены вызовов функции SerOpPluAllRead() с вызовами функции SerOpPluAllRead_Debug() с двумя дополнительными аргументами, именем файла и номером строки, где используется функция.

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

#if 0
// function declaration and prototype for our debug version of the function.
SHORT   SerOpPluAllRead_Debug(PLUIF *pPif, USHORT usLockHnd, char *aszFilePath, int nLineNo);

// macro definition to replace function call using old name with debug function with additional arguments.
#define SerOpPluAllRead(pPif,usLock) SerOpPluAllRead_Debug(pPif,usLock,__FILE__,__LINE__)
#else
// standard function declaration that is normally used with builds.
SHORT   SerOpPluAllRead(PLUIF *pPif, USHORT usLockHnd);
#endif

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

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

Определение функции будет выглядеть примерно так: То, что этот источник делает, - это запросить, чтобы препроцессор переименовал функцию SerOpPluAllRead() в SerOpPluAllRead_Debug() и SerOpPluAllRead_Debug() список аргументов, чтобы включить два дополнительных аргумента, указатель на имя файла, в котором была вызвана функция, и номер строки в файле, в котором используется эта функция.

#if defined(SerOpPluAllRead)
// forward declare the replacement function which we will call once we create our log.
SHORT    SerOpPluAllRead_Special(PLUIF *pPif, USHORT usLockHnd);

SHORT    SerOpPluAllRead_Debug(PLUIF *pPif, USHORT usLockHnd, char *aszFilePath, int nLineNo)
{
    int iLen = 0;
    char  xBuffer[256];

    // only print the last 30 characters of the file name to shorten the logs.
    iLen = strlen (aszFilePath);
    if (iLen > 30) {
        iLen = iLen - 30;
    }
    else {
        iLen = 0;
    }

    sprintf (xBuffer, "SerOpPluAllRead_Debug(): husHandle = %d, File %s, lineno = %d", pPif->husHandle, aszFilePath + iLen, nLineNo);
    IssueDebugLog(xBuffer);

    // now that we have issued the log, continue with standard processing.
    return SerOpPluAllRead_Special(pPif, usLockHnd);
}

// our special replacement function name for when we are generating logs.
SHORT    SerOpPluAllRead_Special(PLUIF *pPif, USHORT usLockHnd)
#else
// standard, normal function name (signature) that is replaced with our debug version.
SHORT   SerOpPluAllRead(PLUIF *pPif, USHORT usLockHnd)
#endif
{
    if (STUB_SELF == SstReadAsMaster()) {
        return OpPluAllRead(pPif, usLockHnd);
    } 
    return OP_NOT_MASTER;
}

Включение исходного файла

Наиболее распространенными применениями директив #include preprocessing являются следующие:

#include <stdio.h>
#include "myheader.h"

#include заменяет инструкцию содержимым указанного файла. Угловые скобки (<>) относятся к файлам заголовков, установленным в системе, а кавычки ("") предназначены для файлов, предоставленных пользователем.

Макросы сами могут развернуть другие макросы один раз, так как этот пример иллюстрирует:

#if VERSION == 1
    #define INCFILE  "vers1.h"
#elif VERSION == 2
    #define INCFILE  "vers2.h"
    /*  and so on */
#else
    #define INCFILE  "versN.h"
#endif
/* ... */
#include INCFILE

Замена макросов

Простейшей формой замены макросов является определение manifest constant , как в

#define ARRSIZE 100
int array[ARRSIZE];

Это определяет функционально-подобный макрос, который умножает переменную на 10 и сохраняет новое значение:

#define TIMES10(A) ((A) *= 10)

double b = 34;
int c = 23;

TIMES10(b);   // good: ((b) *= 10);
TIMES10(c);   // good: ((c) *= 10);
TIMES10(5);   // bad:  ((5) *= 10);

Замена выполняется до любой другой интерпретации текста программы. При первом вызове TIMES10 имя A из определения заменяется на b а затем расширенный текст помещается вместо вызова. Заметим, что это определение TIMES10 не эквивалентно

#define TIMES10(A) ((A) = (A) * 10)

потому что это могло бы оценить замену A , дважды, что может иметь нежелательные побочные эффекты.

Далее определяется функционально-подобный макрос, значение которого является максимумом его аргументов. Он имеет преимущества работы для любых совместимых типов аргументов и генерации встроенного кода без накладных расходов на вызов функции. У него есть недостатки, связанные с оценкой одного или другого из его аргументов во второй раз (включая побочные эффекты) и генерирования большего количества кода, чем функция, если она вызывается несколько раз.

#define max(a, b) ((a) > (b) ? (a) : (b))

int maxVal = max(11, 43);              /* 43 */
int maxValExpr = max(11 + 36, 51 - 7); /* 47 */

/* Should not be done, due to expression being evaluated twice */
int j = 0, i = 0;
int sideEffect = max(++i, ++j);       /* i == 4 */

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

Обильные круглые скобки в макрорасширениях (правая часть определения) гарантируют, что аргументы и результирующее выражение связаны должным образом и хорошо вписываются в контекст, в котором вызывается макрос.

Директива об ошибках

Если препроцессор встречает директиву #error , компиляция прекращается и включается диагностическое сообщение.

#define DEBUG

#ifdef DEBUG
#error "Debug Builds Not Supported"
#endif

int main(void) {
    return 0;
}

Возможный выход:

$ gcc error.c
error.c: error: #error "Debug Builds Not Supported"

#if 0 для блокировки разделов кода

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

/* Block comment around whole function to keep it from getting used.
 * What's even the purpose of this function?
int myUnusedFunction(void)
{
    int i = 5;
    return i;
}
*/

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

/* Block comment around whole function to keep it from getting used.
 * What's even the purpose of this function?
int myUnusedFunction(void)
{
    int i = 5;

    /* Return 5 */
    return i;
}
*/ 

В предыдущем примере последние две строки функции и последний '* /' рассматриваются компилятором, поэтому он компилируется с ошибками. Более безопасный метод - использовать #if 0 вокруг кода, который вы хотите заблокировать.

#if 0
/* #if 0 evaluates to false, so everything between here and the #endif are
 * removed by the preprocessor. */
int myUnusedFunction(void)
{
    int i = 5;
    return i;
}
#endif

Преимущество этого в том, что, когда вы хотите вернуться и найти код, гораздо проще выполнить поиск «#if 0», чем поиск всех ваших комментариев.

Еще одно очень важное преимущество заключается в том, что вы можете вставлять код комментария с #if 0 . Это невозможно сделать с комментариями.

Альтернативой использованию #if 0 является использование имени, которое не будет #defined но более подробно #defined почему код блокируется. Например, если есть функция, которая кажется бесполезным мертвым кодом, вы можете использовать #if defined(POSSIBLE_DEAD_CODE) или #if defined(FUTURE_CODE_REL_020201) для кода, необходимого после того, как будут установлены другие функции или что-то подобное. Затем, когда вы возвращаетесь, чтобы удалить или включить этот источник, эти разделы источника легко найти.

Маркировка токенов

Вставка меток позволяет склеить два макро аргумента. Например, front##back frontback . Известный пример - это заголовок <TCHAR.H> Win32. В стандарте C можно написать L"string" чтобы объявить широкую строку символов. Тем не менее, Windows API позволяет конвертировать между широкими символьными строками и узкими символьными строками просто #define UNICODE . Чтобы реализовать строковые литералы, TCHAR.H использует это

#ifdef UNICODE
#define TEXT(x) L##x
#endif

Всякий раз, когда пользователь записывает TEXT("hello, world") и UNICODE, препроцессор C объединяет L и аргумент макроса. L с "hello, world" дает L"hello, world" .

Предопределенные макросы

Предопределенный макрос - это макрос, который уже понимается процессором C без программы, требующей ее определения. Примеры включают

Обязательные предопределенные макросы

  • __FILE__ , который дает имя файла текущего исходного файла (строковый литерал),
  • __LINE__ для текущего номера строки (целочисленная константа),
  • __DATE__ для даты компиляции (строковый литерал)
  • __TIME__ для времени компиляции (строковый литерал).

Существует также связанный предопределенный идентификатор __func__ (ISO / IEC 9899: 2011 §6.4.2.2), который не является макросом:

Идентификатор __func__ должен быть неявно объявлен переводчиком так, как если бы сразу после открытия скобки каждого определения функции было объявлено:

 static const char __func__[] = "function-name";

, где function-name - это имя лексически-охватывающей функции.

__FILE__ , __LINE__ и __func__ особенно полезны для целей отладки. Например:

fprintf(stderr, "%s: %s: %d: Denominator is 0", __FILE__, __func__, __LINE__);

Компиляторы Pre-C99 могут поддерживать или не поддерживать __func__ или могут иметь макрос, который действует так же, как и по-другому. Например, gcc использовал __FUNCTION__ в режиме C89.

В приведенных ниже макросах можно задать подробные сведения о реализации:

  • __STDC_VERSION__ версия стандарта C. Это постоянное целое число, использующее формат yyyymmL (значение 201112L для C11, значение 199901L для C99, оно не было определено для C89 / C90)
  • __STDC_HOSTED__ 1 если это хостинг-реализация, иначе 0 .
  • __STDC__ Если 1 , реализация соответствует стандарту C.

Другие предварительно определенные макросы (не обязательно)

ISO / IEC 9899: 2011 §6.10.9.2 Макросы окружения:

  • __STDC_ISO_10646__ Целочисленная константа формы yyyymmL (например, 199712L). Если этот символ определен, то каждый символ в кодировке Unicode, заданный при хранении в объекте типа wchar_t , имеет то же значение, что и короткий идентификатор этого символа. Требуемый Unicode набор состоит из всех символов, которые определены ISO / IEC 10646, а также всех поправок и технических исправлений на указанный год и месяц. Если используется некоторая другая кодировка, макрос не должен определяться, а фактическая кодировка используется для реализации.

  • __STDC_MB_MIGHT_NEQ_WC__ Целочисленная константа 1, предназначенная для указания того, что в кодировке для wchar_t член базового набора символов не должен иметь кодового значения, равного его значению, когда он используется как одиночный символ в целочисленной символьной константе.

  • __STDC_UTF_16__ Целочисленная константа 1, предназначенная для указания того, что значения типа char16_t кодируются в кодировке UTF-16. Если используется некоторая другая кодировка, макрос не должен определяться, а фактическая кодировка используется для реализации.

  • __STDC_UTF_32__ Целая константа 1, предназначенная для указания того, что значения типа char32_t кодируются в кодировке UTF-32. Если используется некоторая другая кодировка, макрос не должен определяться, а фактическая кодировка используется для реализации.

ISO / IEC 9899: 2011 §6.10.8.3. Условные функциональные макросы

  • __STDC_ANALYZABLE__ Целая константа 1, предназначенная для указания соответствия спецификациям в приложении L (Анализируемость).
  • __STDC_IEC_559__ Целая константа 1, предназначенная для указания соответствия спецификациям в приложении F (IEC 60559 с плавающей точкой арифметики).
  • __STDC_IEC_559_COMPLEX__ Целочисленная константа 1, предназначенная для указания соответствия спецификациям в приложении G (совместимая с IEC 60559 комплексная арифметика).
  • __STDC_LIB_EXT1__ Целая константа 201112L , предназначенная для указания поддержки расширений, определенных в приложении K (Интерфейсы проверки границ).
  • __STDC_NO_ATOMICS__ Целая константа 1, предназначенная для указания того, что реализация не поддерживает атомные типы (включая классификатор типа _Atomic ) и заголовок <stdatomic.h> .
  • __STDC_NO_COMPLEX__ Целочисленная константа 1, предназначенная для указания того, что реализация не поддерживает сложные типы или заголовок <complex.h> .
  • __STDC_NO_THREADS__ Целочисленная константа 1, предназначенная для указания того, что реализация не поддерживает заголовок <threads.h> .
  • __STDC_NO_VLA__ Целочисленная константа 1, предназначенная для указания того, что реализация не поддерживает массивы переменной длины или измененные типы.

Заголовки включают охранников

Практически каждый заголовочный файл должен следить за идиомой include guard :

мой-заголовок-file.h

#ifndef MY_HEADER_FILE_H
#define MY_HEADER_FILE_H

// Code body for header file

#endif

Это гарантирует, что когда вы #include "my-header-file.h" в нескольких местах, вы не получаете дубликатов деклараций функций, переменных и т. Д. Представьте себе следующую иерархию файлов:

Заголовок-1.h

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

Заголовок-2.h

#include "header-1.h"

int myFunction2(MyStruct *value);

main.c

#include "header-1.h"
#include "header-2.h"

int main() {
    // do something
}

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

Заголовок-1.h

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#endif

Заголовок-2.h

#ifndef HEADER_2_H
#define HEADER_2_H

#include "header-1.h"

int myFunction2(MyStruct *value);

#endif

main.c

#include "header-1.h"
#include "header-2.h"

int main() {
    // do something
}

Затем это расширилось бы до:

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#endif

#ifndef HEADER_2_H
#define HEADER_2_H

#ifndef HEADER_1_H // Safe, since HEADER_1_H was #define'd before.
#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#endif

int myFunction2(MyStruct *value);

#endif

int main() {
    // do something
}

Когда компилятор достигает второго включения header- HEADER_1_H , HEADER_1_H уже был определен предыдущим включением. Эрго, это сводится к следующему:

#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#define HEADER_2_H

int myFunction2(MyStruct *value);

int main() {
    // do something
}

И, следовательно, ошибка компиляции отсутствует.

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


Если детали структуры не были включены в заголовок, объявленный тип был бы неполным или непрозрачным . Такие типы могут быть полезными, скрывая детали реализации от пользователей функций. Для многих целей тип FILE в стандартной библиотеке C можно рассматривать как непрозрачный тип (хотя он обычно не является непрозрачным, так что реализация макросов стандартных функций ввода-вывода может использовать внутренние элементы структуры). В этом случае header-1.h может содержать:

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct MyStruct MyStruct;

int myFunction(MyStruct *value);

#endif

Обратите внимание, что структура должна иметь имя тега (здесь MyStruct - это пространство имен тегов, отдельно от пространства имен обычных идентификаторов имени typedef MyStruct ) и что { … } опущен. Это говорит о том, что существует структура struct MyStruct и для нее есть псевдоним MyStruct .

В файле реализации детали структуры могут быть определены для завершения типа:

struct MyStruct {
    …
};

Если вы используете C11, вы можете повторить typedef struct MyStruct MyStruct; объявление, не вызывая ошибки компиляции, но более ранние версии C будут жаловаться. Следовательно, по-прежнему лучше использовать идиому include guard, хотя в этом примере было бы необязательно, если бы код был только компилирован с компиляторами, поддерживающими C11.


Многие компиляторы поддерживают директиву #pragma once , которая имеет те же результаты:

мой-заголовок-file.h

#pragma once

// Code for header file

Однако #pragma once не является частью стандарта C, поэтому код менее портативен, если вы его используете.


Несколько заголовков не используют идиому include guard. Один конкретный пример - стандартный заголовок <assert.h> . Он может быть включен несколько раз в одну единицу перевода, и эффект от этого зависит от того, определяется ли макрос NDEBUG каждый раз, когда заголовок включен. Иногда у вас может быть аналогичное требование; таких случаев будет мало и далеко. Как правило, ваши заголовки должны быть защищены включением охранной идиомы.

Внедрение

Мы также можем использовать макросы для упрощения чтения и записи кода. Например, мы можем реализовать макросы для реализации конструкции foreach в C для некоторых структур данных, таких как одиночные и двусвязные списки, очереди и т. Д.

Вот небольшой пример.

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

struct LinkedListNode
{
    int data;
    struct LinkedListNode *next;
};

#define FOREACH_LIST(node, list) \
     for (node=list; node; node=node->next)

/* Usage */
int main(void)
{
    struct LinkedListNode *list, **plist = &list, *node;
    int i;

    for (i=0; i<10; i++)
    {
         *plist = malloc(sizeof(struct LinkedListNode));
         (*plist)->data = i;
         (*plist)->next = NULL;
         plist          = &(*plist)->next;
    }

    /* printing the elements here */
    FOREACH_LIST(node, list)
    {
        printf("%d\n", node->data);
    }
}

Вы можете создать стандартный интерфейс для таких структур данных и написать общую реализацию FOREACH как:

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

typedef struct CollectionItem_
{
    int data;
    struct CollectionItem_ *next;
} CollectionItem;

typedef struct Collection_
{
    /* interface functions */
    void* (*first)(void *coll);
    void* (*last) (void *coll);
    void* (*next) (void *coll, CollectionItem *currItem);

    CollectionItem *collectionHead;
    /* Other fields */
} Collection;

/* must implement */
void *first(void *coll)
{
    return ((Collection*)coll)->collectionHead;
}

/* must implement */
void *last(void *coll)
{
    return NULL;
}

/* must implement */
void *next(void *coll, CollectionItem *curr)
{
    return curr->next;
}

CollectionItem *new_CollectionItem(int data)
{
    CollectionItem *item = malloc(sizeof(CollectionItem));
    item->data = data;
    item->next = NULL;
    return item;
}

void Add_Collection(Collection *coll, int data)
{
    CollectionItem **item = &coll->collectionHead;
    while(*item)
        item = &(*item)->next;
    (*item) = new_CollectionItem(data);
}

Collection *new_Collection()
{
    Collection *nc = malloc(sizeof(Collection));
    nc->first = first;
    nc->last  = last;
    nc->next  = next;
    return nc;
}

/* generic implementation */
#define FOREACH(node, collection)                      \
    for (node  = (collection)->first(collection);      \
         node != (collection)->last(collection);       \
         node  = (collection)->next(collection, node))

int main(void)
{
    Collection *coll = new_Collection();
    CollectionItem *node;
    int i;

    for(i=0; i<10; i++)
    {
         Add_Collection(coll, i);
    }

    /* printing the elements here */
    FOREACH(node, coll)
    {
        printf("%d\n", node->data);
    }
}

Чтобы использовать эту общую реализацию, просто выполните эти функции для своей структуры данных.

1.  void* (*first)(void *coll);
2.  void* (*last) (void *coll);
3.  void* (*next) (void *coll, CollectionItem *currItem);

__cplusplus для использования C-externals в коде C ++, скомпилированном с C ++-name mangling

Бывают случаи, когда включаемый файл должен генерировать другой вывод из препроцессора в зависимости от того, является ли компилятор компилятором C или компилятором C ++ из-за различий в языке.

Например, функция или другая внешняя определяется в исходном файле C, но используется в исходном файле C ++. Поскольку C ++ использует управление именами (или украшение имен), чтобы генерировать уникальные имена функций на основе типов аргументов функции, объявление функции C, используемое в исходном файле C ++, приведет к ошибкам ссылок. Компилятор C ++ изменит указанное внешнее имя для выхода компилятора, используя правила управления именами для C ++. Результатом являются ошибки ссылок из-за внешних символов, которые не найдены, когда вывод компилятора C ++ связан с выходом компилятора C.

Поскольку компиляторы C не обрабатывают имя, но компиляторы C ++ делают для всех внешних ярлыков (имен функций или имен переменных), сгенерированных компилятором C ++, был введен предопределенный макрос препроцессора __cplusplus , чтобы разрешить обнаружение компилятора.

Чтобы обойти эту проблему несовместимого вывода компилятора для внешних имен между C и C ++, макрос __cplusplus определен в препроцессоре C ++ и не определен в препроцессоре C. Это имя макроса можно использовать с условной препроцессором директивы #ifdef или #if с помощью оператора defined() чтобы определить, компилируется ли исходный код или файл include как C ++ или C.

#ifdef __cplusplus
printf("C++\n");
#else
printf("C\n");
#endif

Или вы можете использовать

#if defined(__cplusplus)
printf("C++\n");
#else
printf("C\n");
#endif

Чтобы указать правильное имя функции функции из исходного файла C, скомпилированного с помощью компилятора C, который используется в исходном файле C ++, вы можете проверить определенную константу __cplusplus , чтобы вызвать extern "C" { /* ... */ }; для использования, чтобы объявить С внешними, когда заголовочный файл включен в исходный файл на C ++. Однако при компиляции с компилятором C extern "C" { */ ... */ }; не используется. Эта условная компиляция необходима, потому что extern "C" { /* ... */ }; действителен в C ++, но не в C.

#ifdef __cplusplus
// if we are being compiled with a C++ compiler then declare the
// following functions as C functions to prevent name mangling.
extern "C" {
#endif

// exported C function list.
int foo (void);

#ifdef __cplusplus
// if this is a C++ compiler, we need to close off the extern declaration.
};
#endif

Функциональные макросы

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

#ifdef DEBUG
# define LOGFILENAME "/tmp/logfile.log"

# define LOG(str) do {                            \
  FILE *fp = fopen(LOGFILENAME, "a");            \
  if (fp) {                                       \
    fprintf(fp, "%s:%d %s\n", __FILE__, __LINE__, \
                 /* don't print null pointer */   \
                 str ?str :"<null>");             \
    fclose(fp);                                   \
  }                                               \
  else {                                          \
    perror("Opening '" LOGFILENAME "' failed");   \
  }                                               \
} while (0)
#else
  /* Make it a NOOP if DEBUG is not defined. */
# define LOG(LINE) (void)0
#endif


#include <stdio.h>

int main(int argc, char* argv[])
{
    if (argc > 1)
        LOG("There are command line arguments");
    else
        LOG("No command line arguments");
    return 0;
}

Здесь в обоих случаях (с DEBUG или нет) вызов ведет себя так же, как функция с типом возврата void . Это гарантирует, что условия if/else интерпретируются как ожидаемые.

В случае DEBUG это реализуется через конструкцию do { ... } while(0) . В другом случае (void)0 - это оператор без побочного эффекта, который просто игнорируется.

Альтернативой для последнего было бы

#define LOG(LINE) do { /* empty */ } while (0)

так что он во всех случаях синтаксически эквивалентен первому.

Если вы используете GCC, вы также можете реализовать макрос функции, который возвращает результат с использованием выражений выражения нестандартного выражения GNU. Например:

#include <stdio.h>

#define POW(X, Y) \
({ \
        int i, r = 1; \
        for (i = 0; i < Y; ++i) \
                r *= X; \
        r; \ // returned value is result of last operation
})

int main(void)
{
        int result;

        result = POW(2, 3); 
        printf("Result: %d\n", result);
}

Матричные аргументы Variadic

C99

Макросы с переменными аргументами:

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

#define debug_print(msg) printf("%s:%d %s", __FILE__, __LINE__, msg)

Некоторые примеры использования:

Функция somefunc() возвращает -1, если не удалось, и 0, если она выполнена успешно, и вызывается из множества разных мест внутри кода:

int retVal = somefunc();

if(retVal == -1)
{
    debug_printf("somefunc() has failed");
}

/* some other code */

 retVal = somefunc();

if(retVal == -1)
{
    debug_printf("somefunc() has failed");
}

Что произойдет, если реализация somefunc() изменится, и теперь она возвращает разные значения, соответствующие различным возможным типам ошибок? Вы все еще хотите использовать макрос отладки и напечатать значение ошибки.

debug_printf(retVal);      /* this would obviously fail */
debug_printf("%d",retVal); /* this would also fail */

Для решения этой проблемы был __VA_ARGS__ макрос __VA_ARGS__ . Этот макрос позволяет использовать несколько параметров X-macro:

Пример:

 #define debug_print(msg, ...) printf(msg, __VA_ARGS__) \
                               printf("\nError occurred in file:line (%s:%d)\n", __FILE__, __LINE)

Использование:

int retVal = somefunc();

debug_print("retVal of somefunc() is-> %d", retVal);

Этот макрос позволяет передавать несколько параметров и печатать их, но теперь он запрещает вам отправлять какие-либо параметры вообще.

debug_print("Hey");

Это вызовет некоторую синтаксическую ошибку, поскольку макрос ожидает хотя бы еще одного аргумента, а препроцессор не будет игнорировать отсутствие запятой в debug_print() . Также debug_print("Hey",); приведет к возникновению синтаксической ошибки, поскольку вы не можете оставить аргумент, переданный макросу пустым.

Чтобы решить эту проблему, был введен макрос ##__VA_ARGS__ , этот макрос утверждает, что если переменные аргументы отсутствуют, запятая удаляется препроцессором из кода.

Пример:

 #define debug_print(msg, ...) printf(msg, ##__VA_ARGS__) \
                               printf("\nError occured in file:line (%s:%d)\n", __FILE__, __LINE)

Использование:

 debug_print("Ret val of somefunc()?");
 debug_print("%d",somefunc());


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow