Какие приложения оператора ## препроцессора и ошибки должны быть рассмотрены? - PullRequest
86 голосов
/ 19 октября 2008

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

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

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

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

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

Ответы [ 13 ]

49 голосов
/ 20 октября 2008

При использовании операторов предварительной обработки 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
46 голосов
/ 19 октября 2008

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

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

#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: использование ## для универсального 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;
14 голосов
/ 20 октября 2008

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

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

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

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

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

6 голосов
/ 20 октября 2008

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

4 голосов
/ 20 октября 2008

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

Ссылка

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

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

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

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

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

2 голосов
/ 20 октября 2008

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



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


2 голосов
/ 20 октября 2008

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

2 голосов
/ 20 октября 2008

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

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

2 голосов
/ 20 октября 2008

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

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

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

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

1 голос
/ 20 мая 2013

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

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

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

#define ADDR_LEFTSHIFT                          0

#define ADDR_WIDTH                              7

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

BITFMASK(ADDR)
...