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

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

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

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

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

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

19.10.2008 19:51:26
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рядом с a __ 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;
47
4.02.2016 18:27:37
Поскольку вы можете объединять строковые литералы во время компиляции, вы можете уменьшить выражение BuildDate std::wstring BuildDate = WIDEN(__DATE__) L" " WIDEN(__TIME__); и неявно создать всю строку сразу.
user666412 15.02.2016 17:54:19

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

UNITTEST(test_name)

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

void __testframework_test_name ()
1
19.10.2008 20:02:51

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

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

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

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

3
4.02.2016 20:18:14

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

6
19.10.2008 22:13:06

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

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

1
19.10.2008 20:15:02

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

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

2
19.10.2008 20:16:43

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

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

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

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

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

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

0
19.10.2008 20:51:40
Что ты пытался сделать с этим? Это будет работать так же хорошо без "##", так как нет необходимости вставлять токены "," в "msg". Вы пытались зашифровать сообщение? Кроме того, ФАЙЛ и ЛИНИЯ должны быть прописными, а не строчными.
bk1e 19.10.2008 20:44:16
Вы действительно правы. Мне нужно найти оригинальный скрипт, чтобы увидеть, как ## был использован. Позор мне, сегодня нет печенья!
ya23 12.12.2008 09:59:59

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

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

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

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

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

14
19.10.2008 21:08:52
Спасибо - я не знал об этом (но тогда я не слишком часто использую эти операторы предварительной обработки ...).
Michael Burr 20.10.2008 01:12:48
По какой-то причине это называется оператором «вставки токена» - цель состоит в том, чтобы по окончании работы получить один токен. Хорошая рецензия.
Mark Ransom 30.04.2009 18:17:49
Когда результат операции вставки токена не является допустимым токеном препроцессора, поведение не определено.
alecov 30.06.2014 15:19:29
Изменения языка, такие как шестнадцатеричные числа с плавающей запятой или (в C ++) разделители цифр и пользовательские литералы, постоянно меняют то, что составляет «действительный токен предварительной обработки», поэтому, пожалуйста, никогда не злоупотребляйте этим! Если вам необходимо разделить токены (на собственном языке), пишите их как два отдельных токена и не полагайтесь на случайные взаимодействия между грамматикой препроцессора и самим языком.
Kerrek SB 16.10.2016 13:06:20

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



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


2
19.10.2008 21:10:19
Я так понимаю, вы подразумеваете под «нестандартным», что компилятор не вставлял строки, а вставлял токены - или он работал бы даже без него ##?
PJTraill 10.06.2015 10:44:27

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

Ссылка на сайт

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

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

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

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

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

4
23.05.2017 12:02:02

Когда вы используете операторы предварительной обработки 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
49
19.10.2008 23:49:02
Для объяснения этого поведения препроцессора, см. Stackoverflow.com/questions/8231966/…
Adam Davis 1.12.2011 20:50:50
@MichaelBurr Я читал твой ответ, и у меня есть сомнения. Почему эта ЛИНИЯ печатает номер строки?
HELP PLZ 26.06.2014 16:51:37
@AbhimanyuAryan: Я не уверен, что это то, о чем вы спрашиваете, но __LINE__это специальное имя макроса, которое заменяется препроцессором с номером текущей строки исходного файла.
Michael Burr 26.06.2014 19:16:15
Было бы здорово, если бы языковые спецификации можно было цитировать / связывать, как здесь
Antonio 19.04.2017 14:02:59

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

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

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

#define ADDR_LEFTSHIFT                          0

#define ADDR_WIDTH                              7

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

BITFMASK(ADDR)
1
20.05.2013 11:42:30