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


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

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

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

так paste(name, 1) создает маркер name1.

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

13 85

13 ответов:

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

интересным использованием в CrashRpt (crash reporting library) является следующее:

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

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

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

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

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


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

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;

одна вещь, чтобы быть в курсе, когда вы используете токен-вставить ('##') или 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

вот что я наткнулся при обновлении до новой версии компилятора:

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

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

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

#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 / 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 по конкатенации имеет более полезную информацию об операторе вставки токенов.

это полезно во всех видах ситуаций, чтобы не повторять себя без необходимости. Ниже приведен пример из исходного кода 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" (и рискуете ошибиться в написании одного из них).

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

ссылке

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

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

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

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

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

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

Он может быть использован для шаблонов:

#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;
};

аналогично можно написать шаблон функции для обхода списка.

Я использую его в программах на 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 )

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

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

Я использую его для домашнего проката assert на нестандартном компиляторе C для embedded:



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


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

UNITTEST(test_name)

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

void __testframework_test_name ()

основное использование-это когда у вас есть соглашение об именах, и вы хотите, чтобы ваш макрос воспользовался этим соглашением об именах. Возможно, у вас есть несколько семейств методов: 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())

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

одна важная польза в вздрагивании:

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

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

#define ADDR_LEFTSHIFT                          0

#define ADDR_WIDTH                              7

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

BITFMASK(ADDR)

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

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

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

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

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

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