Які програми слід використовувати для використання оператора препроцесора ## і що потрібно взяти до уваги?


88

Як згадувалось у багатьох моїх попередніх питаннях, я працюю через K&R і зараз працюю в препроцесорі. Однією з найцікавіших речей - те, чого я ніколи раніше не знав з жодної з попередніх спроб вивчити C - є ##оператор препроцесора. За даними K&R:

Оператор препроцесора ## надає спосіб об'єднання фактичних аргументів під час розширення макросу. Якщо параметр у тексті заміни сусід з a ##, параметр замінюється фактичним аргументом, ##пробіли та навколишні пробіли видаляються, а результат повторно сканується. Наприклад, макрос paste об'єднує два свої аргументи:

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

так paste(name, 1)створює маркер name1.

Як і чому хтось би цим користувався у реальному світі? Які практичні приклади його використання та чи є питання для розгляду?

Відповіді:


47

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;

3
Оскільки ви можете об'єднувати рядкові літерали під час компіляції, ви можете зменшити вираз BuildDate до std::wstring BuildDate = WIDEN(__DATE__) L" " WIDEN(__TIME__); і неявно побудувати весь рядок відразу.
user666412

49

Одне, про що слід пам’ятати, коли ви використовуєте оператори попередньої обробки маркер-вставки (' ##') або строки (' #'), - це те, що ви повинні використовувати додатковий рівень опосередкованості, щоб вони працювали належним чином у всіх випадках.

Якщо ви цього не зробите, а елементи, передані оператору вставки маркерів, є самими макросами, ви отримаєте результати, які, ймовірно, не такі, як ви хочете:

#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
Пояснення щодо такої поведінки препроцесора див. На stackoverflow.com/questions/8231966/…
Адам Девіс,

@MichaelBurr я читав вашу відповідь, і я маю сумніви. Як це ЛІНІЯ друкує номер рядка?
HELP PLZ

3
@AbhimanyuAryan: Я не впевнений, що це те, про що ви запитуєте, але __LINE__це спеціальне ім'я макросу, яке замінюється препроцесором на поточний номер рядка вихідного файлу.
Michael Burr

Було б круто, якби мовні специфікації могли цитуватися / посилатись, як тут
Антоніо

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 про конкатенацію є більше корисної інформації про оператор вставки маркерів.


Дякую - я не знав про це (але тоді я не надто використовую ці оператори попередньої обробки ...).
Michael Burr,

3
Це називається оператором "вставка маркера" з причини - намір полягає в тому, щоб закінчити з одним маркером, коли закінчите. Хороший запис.
Марк Ренсом

Коли результат оператора вставки маркера не є допустимим маркером препроцесора, поведінка не визначена.
alecov

Зміни мови, такі як шістнадцяткові плаваючі символи або (у C ++) роздільники цифр та визначені користувачем літерали, постійно змінюють те, що становить "дійсний маркер попередньої обробки", тому, будь ласка, ніколи не зловживайте цим! Якщо вам доводиться відокремлювати (власну мову) лексеми, будь ласка, напишіть їх як два окремі маркери, і не покладайтесь на випадкові взаємодії між граматикою препроцесора та власною мовою.
Kerrek SB

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"(і ризикувати помилковим написанням одного з них).


4

Попереднє запитання про переповнення стека просило плавного методу генерації подань рядків для констант перерахування без великої кількості помилок перетипування.

Посилання

Моя відповідь на це запитання показала, як застосування невеликої магії препроцесора дозволяє вам визначити ваше перерахування таким чином (наприклад) ...;

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

... З тією перевагою, що розширення макросу не лише визначає перерахування (у .h-файлі), воно також визначає відповідний масив рядків (у .c-файлі);

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

Назва таблиці рядків походить від вставки параметра макросу (тобто кольору) до StringTable за допомогою оператора ##. Такі додатки (хитрощі?), Де оператори # та ## безцінні.


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

Подібним чином ви можете написати шаблон функції для обходу списку.


2

Я використовую його в програмах на С, щоб допомогти правильно застосувати прототипи для набору методів, які повинні відповідати певним умовам викликів. Певним чином, це може бути використано для орієнтації об’єкта бідної людини прямо в 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 тощо.


2

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



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



3
Я вважаю, ви маєте на увазі під "нестандартним", що компілятор не виконував вставку рядків, а вставку маркерів - чи він би працював і без цього ##?
PJTraill

1

Я використовую його для додавання спеціальних префіксів до змінних, визначених макросами. Отже, щось на зразок:

UNITTEST(test_name)

розширюється до:

void __testframework_test_name ()

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

Звичайно, свого роду "мінімальна версія об'єктів" - це не єдиний тип конвенції про іменування, до якої це стосується - майже переважна більшість конвенцій про іменування використовує загальну підрядок для формування імен. Це можуть бути імена функцій (як зазначено вище), або імена полів, імена змінних або щось інше.


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)

0

Це дуже корисно для лісозаготівлі. Ви можете зробити:

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

Або, якщо ваш компілятор не підтримує функції та функції :

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

Вищевказані "функції" реєструють повідомлення та показують, яка саме функція реєструвала повідомлення.

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


1
Що ви намагалися з цим зробити? Він би працював так само добре без "##", оскільки немає потреби token-paste "," to "msg". Ви намагалися надати рядок повідомлення? Крім того, ФАЙЛ і РЯДК мають бути з великої, а не з малої літери.
bk1e

Ти справді маєш рацію. Мені потрібно знайти оригінальний сценарій, щоб побачити, як було використано ##. Ганьба мені, печива сьогодні немає!
ya23
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.