Как упоминалось во многих моих предыдущих вопросах, я работаю через K&R, а сейчас занимаюсь препроцессором. Одна из наиболее интересных вещей - чего я никогда раньше не знал из своих предыдущих попыток изучить C - это ##
оператор препроцессора. Согласно K&R:
Оператор препроцессора
##
предоставляет способ объединения фактических аргументов во время раскрытия макроса. Если параметр в замещающем тексте находится рядом с a##
, параметр заменяется фактическим аргументом,##
удаляются и окружающие пробелы, а результат повторно просматривается. Например, макросpaste
объединяет два своих аргумента:
#define paste(front, back) front ## back
так
paste(name, 1)
создает токенname1
.
Как и зачем кому-то это использовать в реальном мире? Каковы практические примеры его использования и есть ли подводные камни, которые следует учитывать?
std::wstring BuildDate = WIDEN(__DATE__) L" " WIDEN(__TIME__);
и неявно построить сразу всю строку.Одна вещь, о которой следует помнить, когда вы используете операторы предварительной обработки 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
источник
__LINE__
это специальное имя макроса, которое препроцессор заменяет текущим номером строки исходного файла.Вот проблема, с которой я столкнулся при обновлении до новой версии компилятора:
Ненужное использование оператора вставки токена (
##
) не является переносимым и может генерировать нежелательные пробелы, предупреждения или ошибки.Если результат операции вставки токена не является допустимым токеном препроцессора, оператор вставки токена не нужен и, возможно, вреден.
Например, можно попытаться построить строковые литералы во время компиляции с помощью оператора вставки токена:
#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; \ }
Затем мы можем использовать его:
Преимущество состоит в том, что не нужно писать оба
fn_XpmFreeAttributes
и"XpmFreeAttributes"
(и рисковать ошибиться в написании одного из них).источник
В предыдущем вопросе о переполнении стека предлагался плавный метод генерации строковых представлений для констант перечисления без большого количества повторяющихся ошибок.
Ссылка на сайт
Мой ответ на этот вопрос показал, как применение небольшой магии препроцессора позволяет вам определять ваше перечисление следующим образом (например) ...;
... С тем преимуществом, что расширение макроса не только определяет перечисление (в файле .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; };
Аналогичным образом вы можете написать шаблон функции для обхода списка.
источник
Я использую его в программах на C, чтобы помочь правильно применить прототипы для набора методов, которые должны соответствовать какому-то соглашению о вызовах. В некотором смысле это можно использовать для объектной ориентации бедняков по прямой C:
расширяется примерно до этого:
STATUS activeCall_constructor( HANDLE *pInst ) STATUS activeCall_eventHandler( HANDLE *pInst, TOKEN *pEvent ); STATUS activeCall_destructor( HANDLE *pInst );
Это обеспечивает правильную параметризацию для всех "производных" объектов, когда вы:
указанное выше в ваших файлах заголовков и т. д. Это также полезно для обслуживания, если вы даже захотите изменить определения и / или добавить методы к «объектам».
источник
SGlib использует ## для подделки шаблонов в C. Поскольку нет перегрузки функций, ## используется для вставки имени типа в имена сгенерированных функций. Если бы у меня был тип списка с именем list_t, я бы получил функции с именем sglib_list_t_concat и так далее.
источник
Я использую его для домашнего свернутого утверждения на нестандартном компиляторе C для встроенных:
#define ASSERT(exp) if(!(exp)){ \ print_to_rs232("Assert failed: " ## #exp );\ while(1){} //Let the watchdog kill us
источник
##
?Я использую его для добавления пользовательских префиксов к переменным, определяемым макросами. Так что-то вроде:
расширяется до:
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())
Конечно, своего рода «минимальная версия объектов» - не единственный вид соглашения об именах, к которому это применимо - почти в подавляющем большинстве соглашений об именах используется общая подстрока для формирования имен. Это могут быть имена функций (как указано выше), имена полей, имена переменных или что-то еще.
источник
Одно важное использование в WinCE:
#define BITFMASK(bit_position) (((1U << (bit_position ## _WIDTH)) - 1) << (bit_position ## _LEFTSHIFT))
При определении описания бита регистра мы делаем следующее:
#define ADDR_LEFTSHIFT 0 #define ADDR_WIDTH 7
А при использовании BITFMASK просто используйте:
источник
Это очень полезно для ведения журнала. Ты можешь сделать:
#define LOG(msg) log_msg(__function__, ## msg)
Или, если ваш компилятор не поддерживает функцию и функцию :
#define LOG(msg) log_msg(__file__, __line__, ## msg)
Вышеупомянутые «функции» регистрируют сообщение и показывают, какая именно функция зарегистрировала сообщение.
Мой синтаксис C ++ может быть не совсем правильным.
источник