Каковы приложения оператора ## препроцессора и gotchas для рассмотрения?

Как упоминалось во многих моих предыдущих вопросах, я работаю через K & R, и в настоящее время я препроцессор. Одна из наиболее интересных вещей - что-то, чего я никогда раньше не знал из-за моих предыдущих попыток узнать C — является оператором препроцессора ##. Согласно K & R:

Оператор препроцессора ##обеспечивает способ объединения фактических аргументы во время макрорасширения. Если параметр в заменяемом тексте рядом с a ##, параметр заменяется фактическим аргументом, ## и окружающее свободное пространство удаляется, и результат повторно сканируется. Например, макрос pasteобъединяет два аргумента:

#define paste(front, back) front ## back

поэтому paste(name, 1) создает токен name1.

Как и зачем кому-то использовать это в реальном мире? Каковы практические примеры его использования, и есть ли какие-то соображения?

Ответ 1

CrashRpt: использование ## для преобразования макрос многобайтовых строк в Unicode

Интересное использование в CrashRpt (библиотека отчетов о сбоях) следующее:

#define WIDEN2(x) L ## x
#define WIDEN(x) WIDEN2(x)
//Note you need a WIDEN2 so that __DATE__ will evaluate first.

Здесь они хотят использовать двухбайтную строку вместо строки длиной 1 байт. Вероятно, похоже, что это действительно бессмысленно, но они делают это по уважительной причине.

 std::wstring BuildDate = std::wstring(WIDEN(__DATE__)) + L" " + WIDEN(__TIME__);

Они используют его с другим макросом, который возвращает строку с датой и временем.

Ввод L рядом с __ DATE __ даст вам компиляционную ошибку.


Windows: использование ## для общих Unicode или многобайтовых строк

Windows использует что-то вроде следующего:

#ifdef  _UNICODE
    #define _T(x)      L ## x
#else
    #define _T(x) x
#endif

И _T используется везде в коде


Различные библиотеки, использующие имена чистых аксессуаров и модификаторов:

Я также видел, как он использовался в коде для определения аксессуаров и модификаторов:

#define MYLIB_ACCESSOR(name) (Get##name)
#define MYLIB_MODIFIER(name) (Set##name)

Аналогичным образом вы можете использовать этот метод для любых других типов умного создания имен.


Различные библиотеки, используя его для создания нескольких объявлений переменных сразу:

#define CREATE_3_VARS(name) name##1, name##2, name##3
int CREATE_3_VARS(myInts);
myInts1 = 13;
myInts2 = 19;
myInts3 = 77;

Ответ 2

Одна вещь, о которой нужно знать, когда вы используете операции предварительной обработки token-paste ('##') или stringizing ('#'), заключается в том, что вам нужно использовать дополнительный уровень косвенности для них работайте правильно во всех случаях.

Если вы этого не сделаете, и элементы, переданные оператору ввода-вывода, являются самими макросами, вы получите результаты, которые, вероятно, не то, что вы хотите:

#include <stdio.h>

#define STRINGIFY2( x) #x
#define STRINGIFY(x) STRINGIFY2(x)
#define PASTE2( a, b) a##b
#define PASTE( a, b) PASTE2( a, b)

#define BAD_PASTE(x,y) x##y
#define BAD_STRINGIFY(x) #x

#define SOME_MACRO function_name

int main() 
{
    printf( "buggy results:\n");
    printf( "%s\n", STRINGIFY( BAD_PASTE( SOME_MACRO, __LINE__)));
    printf( "%s\n", BAD_STRINGIFY( BAD_PASTE( SOME_MACRO, __LINE__)));
    printf( "%s\n", BAD_STRINGIFY( PASTE( SOME_MACRO, __LINE__)));

    printf( "\n" "desired result:\n");
    printf( "%s\n", STRINGIFY( PASTE( SOME_MACRO, __LINE__)));
}

Выход:

buggy results:
SOME_MACRO__LINE__
BAD_PASTE( SOME_MACRO, __LINE__)
PASTE( SOME_MACRO, __LINE__)

desired result:
function_name21

Ответ 3

Вот что я получил, когда перешел на новую версию компилятора:

Ненужное использование оператора-маркера (##) не переносится и может генерировать нежелательные пробелы, предупреждения или ошибки.

Когда результат действия оператора маркера не является допустимым токеном препроцессора, оператор маркерного ввода не нужен и, возможно, вреден.

Например, можно попытаться построить строковые литералы во время компиляции с помощью оператора маркера:

#define STRINGIFY(x) #x
#define PLUS(a, b) STRINGIFY(a##+##b)
#define NS(a, b) STRINGIFY(a##::##b)
printf("%s %s\n", PLUS(1,2), NS(std,vector));

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

1+2 std::vector

В других компиляторах это будет включать нежелательные пробелы:

1 + 2 std :: vector

Довольно современные версии GCC ( >= 3,3 или около того) не смогут скомпилировать этот код:

foo.cpp:16:1: pasting "1" and "+" does not give a valid preprocessing token
foo.cpp:16:1: pasting "+" and "2" does not give a valid preprocessing token
foo.cpp:16:1: pasting "std" and "::" does not give a valid preprocessing token
foo.cpp:16:1: pasting "::" and "vector" does not give a valid preprocessing token

Решение состоит в том, чтобы опустить оператор маркирования маркеров при конкатенации токенов препроцессора операторам C/С++:

#define STRINGIFY(x) #x
#define PLUS(a, b) STRINGIFY(a+b)
#define NS(a, b) STRINGIFY(a::b)
printf("%s %s\n", PLUS(1,2), NS(std,vector));

В в документации по GCC CPP, посвященной конкатенации, содержится более полезная информация о операторе маркера.

Ответ 4

Это полезно во всех ситуациях, чтобы не повторять себя бесполезно. Ниже приведен пример из исходного кода Emacs. Мы хотели бы загрузить несколько функций из библиотеки. Функция "foo" должна быть назначена fn_foo и т.д. Мы определяем следующий макрос:

#define LOAD_IMGLIB_FN(lib,func) {                                      \
    fn_##func = (void *) GetProcAddress (lib, #func);                   \
    if (!fn_##func) return 0;                                           \
  }

Затем мы можем использовать его:

LOAD_IMGLIB_FN (library, XpmFreeAttributes);
LOAD_IMGLIB_FN (library, XpmCreateImageFromBuffer);
LOAD_IMGLIB_FN (library, XpmReadFileToImage);
LOAD_IMGLIB_FN (library, XImageFree);

Преимущество не в том, чтобы писать как fn_XpmFreeAttributes, так и "XpmFreeAttributes" (и риск ошибочно наложил один из них).

Ответ 5

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

Ссылка

Мой ответ на этот вопрос показал, как использование малой препроцессорной магии позволяет вам определить ваше перечисление, подобное этому (например)...;

ENUM_BEGIN( Color )
  ENUM(RED),
  ENUM(GREEN),
  ENUM(BLUE)
ENUM_END( Color )

... С учетом того, что расширение макроса не только определяет перечисление (в файле .h), оно также определяет соответствующий массив строк (в .c файле);

const char *ColorStringTable[] =
{
  "RED",
  "GREEN",
  "BLUE"
};

Имя таблицы строк происходит от вставки параметра макроса (то есть цвета) в StringTable с помощью оператора ##. Приложения (трюки?), Как это, где операторы # и ## неоценимы.

Ответ 6

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

Его можно использовать для шаблонов:

#define LINKED_LIST(A) struct list##_##A {\
A value; \
struct list##_##A *next; \
};

В этом случае LINKED_LIST (int) даст вам

struct list_int {
int value;
struct list_int *next;
};

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

Ответ 7

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

SCREEN_HANDLER( activeCall )

расширяется примерно так:

STATUS activeCall_constructor( HANDLE *pInst )
STATUS activeCall_eventHandler( HANDLE *pInst, TOKEN *pEvent );
STATUS activeCall_destructor( HANDLE *pInst );

Это обеспечивает правильную параметризацию для всех "производных" объектов, когда вы делаете:

SCREEN_HANDLER( activeCall )
SCREEN_HANDLER( ringingCall )
SCREEN_HANDLER( heldCall )

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

Ответ 8

SGlib использует ##, чтобы в основном выпустить шаблоны в C. Поскольку нет перегрузки функций, ## используется для склеивания типа имя в имена сгенерированных функций. Если бы у меня был тип списка, называемый list_t, я бы получил функции с именем sglib_list_t_concat и т.д.

Ответ 9

Я использую его для отсканированного home assert на нестандартном компиляторе C для встроенных:



#define ASSERT(exp) if(!(exp)){ \
                      print_to_rs232("Assert failed: " ## #exp );\
                      while(1){} //Let the watchdog kill us 


Ответ 10

Я использую его для добавления пользовательских префиксов к переменным, определенным макросами. Так что-то вроде:

UNITTEST(test_name)

расширяется до:

void __testframework_test_name ()

Ответ 11

Основное использование - это когда у вас есть соглашение об именах, и вы хотите, чтобы ваш макрос использовал это соглашение об именах. Возможно, у вас есть несколько семейств методов: image_create(), image_activate() и image_release() также file_create(), file_activate(), file_release() и mobile_create(), mobile_activate() и mobile_release().

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

#define LIFECYCLE(name, func) (struct name x = name##_create(); name##_activate(x); func(x); name##_release())

Конечно, своего рода "минимальная версия объектов" - это не единственное соглашение об именах, к которому это относится - почти для большинства соглашений об именах используется общая подстрока для формирования имен. Я мог бы использовать имена (как указано выше) или имена полей, имена переменных или большинство других.

Ответ 12

Одно важное использование в WinCE:

#define BITFMASK(bit_position) (((1U << (bit_position ## _WIDTH)) - 1) << (bit_position ## _LEFTSHIFT))

При определении описания бит регистра мы делаем следующее:

#define ADDR_LEFTSHIFT                          0

#define ADDR_WIDTH                              7

И при использовании BITFMASK просто используйте:

BITFMASK(ADDR)

Ответ 13

Это очень полезно для ведения журнала. Вы можете сделать:

#define LOG(msg) log_msg(__function__, ## msg)

Или, если ваш компилятор не поддерживает функцию и func:

#define LOG(msg) log_msg(__file__, __line__, ## msg)

Вышеупомянутое сообщение "функции" регистрирует сообщение и показывает точно, какая функция зарегистрировала сообщение.

Синтаксис С++ может быть не совсем правильным.