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());