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

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

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

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

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

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

13 ответов

Решение

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;

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

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

#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 )

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

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

Имя таблицы строк происходит от вставки параметра макроса (т. Е. Color) в 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;
};

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

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

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

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



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

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

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

UNITTEST(test_name)

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

void __testframework_test_name ()

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

#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)

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

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

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

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

Другие вопросы по тегам