Як, власне, працює подвійний струнний трюк?


84

Принаймні деякі препроцесори C дозволяють вам обробляти значення макросу, а не його ім'я, передаючи його через один функціональний макрос іншому, який його обробляє:

#define STR1(x) #x
#define STR2(x) STR1(x)
#define THE_ANSWER 42
#define THE_ANSWER_STR STR2(THE_ANSWER) /* "42" */

Приклади випадків використання тут .

Це справді працює, принаймні в GCC та Clang (обидва з -std=c99), але я не впевнений, як це працює в умовах C-стандарту.

Чи така поведінка гарантована C99?
Якщо так, то як це гарантує C99?
Якщо ні, то в який момент поведінка переходить від визначеного С до визначеного GCC?


1
Якщо ви говорите "принаймні деякі", чи означає це, що ви бачили такого, де він не працює? Я готовий написати звіт про помилку постачальнику.
Йенс,

@Jens: Ні; Я не мав. Кожен компілятор, яким я користувався (а саме GCC і Clang) реалізує цю поведінку.
Пітер Хоузі,

Відповіді:


80

Так, це гарантовано.

Це працює, оскільки самі аргументи для макросів розширюються за допомогою макросів , за винятком випадків, коли ім'я аргументу макроса відображається в тілі макроса зі значенням stringifier # або token-paster ##.

6.10.3.1/1:

... Після того, як були визначені аргументи для виклику функціонального макросу, відбувається заміна аргументів. Параметр у списку заміни, якщо йому не передує маркер попередньої обробки # або ## або не слідує маркер попередньої обробки ## (див. Нижче), замінюється відповідним аргументом після розширення всіх макросів, що містяться в ньому ...

Отже, якщо ви це зробите, STR1(THE_ANSWER)ви отримаєте "THE_ANSWER", оскільки аргумент STR1 не розширений за допомогою макросу. Тим НЕ менше, аргумент Str2 є макро-розширена , коли вона заміщена в визначення Str2, який , отже , дає Str1 аргумент 42, з результатом «42».


21

Як зазначає Стів, це гарантовано, і воно гарантується ще зі стандарту C89 - це був стандарт, кодифікований операторами # і ## в макросах і мандатами рекурсивного розширення макросів у аргументах перед заміною їх у тілі тоді і тільки тоді, коли тіло не застосовує # або ## до аргументу. C99 в цьому відношенні не змінився з C89.


+1 за те, що не поспішаючи підтвердити, що він також є частиною C89. Деякі з нас піклуються про портативність, включаючи створення коду, який можуть використовувати люди, що компілюють у режимі C89 - буквально кілька років тому випуски gcc все ще були за замовчуванням C89 (плюс розширення gcc для справедливості), MSVC останній раз я чув, що підтримує лише C89 і вбудований або застарілі системи також іноді мають лише компілятори C89. C89 забезпечує чудову портативність "першого поверху", найменш загального стандарту знаменника, на який люди можуть орієнтуватися, щоб скомпілювати та застосувати будь-що для практичного використання сьогодні. Тож дуже приємно бачити, як це запам’ятали.
mtraceur
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.