C Language
Препроцессор и макросы
Поиск…
Вступление
Все команды препроцессора начинаются с символа хеша (фунта) # . Макрос 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
Макросы с переменными аргументами:
Предположим, вы хотите создать некоторый макрос печати для отладки вашего кода, давайте возьмем этот макрос в качестве примера:
#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());