2008-10-19 4 views
82

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

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

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

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

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

ответ

44

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 __ даст вам компиляционную ошибку.


Окна: Использование ## для общего 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; 
+3

Так как вы можете сцепить строковые литералы во время компиляции, можно привести выражение BuildDate в `станд :: wstring BuildDate = Виден (__DATE__) L "" WIDEN (__ TIME __); "и неявно построить всю строку сразу. – user666412 2016-02-15 17:54:19

1

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

UNITTEST(test_name) 

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

void __testframework_test_name() 
3

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

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

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

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

6

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

1

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

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

2

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

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

0

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

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

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

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

выше «функции» регистрирует сообщение и точно показывает, какие функции вошли сообщение ,

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

+1

Что вы пытались сделать с этим? Он будет работать так же хорошо, как и «##», так как нет необходимости прикреплять токены «,» до «msg». Вы пытались передать msg? Кроме того, __FILE__ и __LINE__ должны быть в верхнем, а не в нижнем регистре. – bk1e 2008-10-19 20:44:16

+0

Ты прав. Мне нужно найти исходный скрипт, чтобы увидеть, как использовался ##. Позор мне, сегодня не печенье! – ya23 2008-12-12 09:59:59

14

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

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

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

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

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

+0

Спасибо - я не знал об этом (но тогда я не слишком часто использую эти операторы предварительной обработки ...). – 2008-10-20 01:12:48

+3

Это называется оператором «token pasting» по какой-либо причине - цель состоит в том, чтобы закончить с помощью одного токена, когда вы закончите. Хорошая запись. – 2009-04-30 18:17:49

2

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

 


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

 
4

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

Link

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

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

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

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

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

46

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

Одно важное применение в WinCE:

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

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

#define ADDR_LEFTSHIFT       0 

#define ADDR_WIDTH        7 

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

BITFMASK(ADDR)