Як я можу показати значення #define під час компіляції?


124

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

#error BOOST_VERSION

але препроцесор не розширює BOOST_VERSION.

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


7
Для майбутніх відвідувачів ... Кріс Баррі надає узагальнене рішення в кінці (позбавлене конкретних матеріалів Boost).
jww

Відповіді:


118

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

Це можна зробити в GCC за допомогою оператора stringify "#", але для цього потрібно два етапи.

#define XSTR(x) STR(x)
#define STR(x) #x

Значення макросу може бути відображено за допомогою:

#pragma message "The value of ABC: " XSTR(ABC)

Див.: 3.4 Структуризація в онлайн-документації gcc.

Як це працює:

Препроцесор розуміє цитовані рядки та обробляє їх відмінно від звичайного тексту. З'єднання струн є прикладом цього спеціального лікування. Прагма повідомлення потребує аргументу, який є цитованим рядком. Якщо в аргументі є більше одного компонента, то всі вони повинні бути рядками, щоб можна було застосувати об'єднання рядків. Препроцесор ніколи не може припустити, що до котирується рядка слід ставитися так, ніби його цитують. Якщо це було тоді:

#define ABC 123
int n = ABC;

не збирався б.

Тепер розглянемо:

#define ABC abc
#pragma message "The value of ABC is: " ABC

що еквівалентно

#pragma message "The value of ABC is: " abc

Це спричиняє попередження попереднього процесу, оскільки abc (без котирування) не може бути з'єднано з попереднім рядком.

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

#define STR(x) #x
char *s1 = "abc";
char *s2 = STR(abc);

присвоїть однакові значення s1 і s2. Якщо ви запускаєте gcc -E, ви можете бачити це у висновку. Можливо, STR краще буде назвати щось на кшталт ENQUOTE.

Це вирішує проблему розміщення лапок навколо котируваного елемента, тепер проблема полягає в тому, що якщо аргумент є макросом, макрос не буде розширено. Саме тому потрібен другий макрос. XSTR розширює свій аргумент, потім викликає STR, щоб розмістити розширене значення в лапки.


3
Мені цікаво, чому для цього потрібні два етапи
Вінсент Фурмонд

4
@VincentFourmond Без етапу XSTR макрос не розширюється. Отже, якщо ви зробили #define ABC 42 \ n STR (ABC), ви отримаєте "ABC". Дивіться gcc.gnu.org/onlinedocs/cpp/Stringification.html
rob05c

Це також чудово працює з Xcode 8, наприклад, заміною ABC на __IPHONE_9_3.
funroll

Термінологія GCC, схоже, змінилася, а з нею і URL-адреса, яка тепер https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html#Stringizing
Кріс Баррі

119

BOOST_PP_STRINGIZE здається відмінним рішенням для C ++, але не для звичайного C.

Ось моє рішення для GNU CPP:

/* Some test definition here */
#define DEFINED_BUT_NO_VALUE
#define DEFINED_INT 3
#define DEFINED_STR "ABC"

/* definition to expand macro then apply to pragma message */
#define VALUE_TO_STRING(x) #x
#define VALUE(x) VALUE_TO_STRING(x)
#define VAR_NAME_VALUE(var) #var "="  VALUE(var)

/* Some example here */
#pragma message(VAR_NAME_VALUE(NOT_DEFINED))
#pragma message(VAR_NAME_VALUE(DEFINED_BUT_NO_VALUE))
#pragma message(VAR_NAME_VALUE(DEFINED_INT))
#pragma message(VAR_NAME_VALUE(DEFINED_STR))

Вищевикладені визначення призводять до:

test.c:10:9: note: #pragma message: NOT_DEFINED=NOT_DEFINED
test.c:11:9: note: #pragma message: DEFINED_BUT_NO_VALUE=
test.c:12:9: note: #pragma message: DEFINED_INT=3
test.c:13:9: note: #pragma message: DEFINED_STR="ABC"

Для змінних "визначено як interger" , "визначено як рядок" та "визначені, але не мають значення" , вони працюють чудово. Тільки для змінної "не визначено" вони відображали точно так само, як і вихідну назву змінної. Ви повинні звикнути до цього - а може, хтось може запропонувати краще рішення.


відмінно! Будь-який досвід роботи в ARM RVCT? Здається, немає функції "Stringification" як GCC infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0491c/…
xdan

2
Прекрасне рішення. Однак, якщо я хочу відобразити розмір обчисленого часу компіляції, наприклад розмір складної структури, чи можна це зробити? Метод, запропонований у цій відповіді, здається, генерує DEFINED_INT=(sizeof(MY_STRUCT)), не sizeofоцінюючи оператора.
Карл

(Додавання коментаря: не несподівано, оскільки саме компілятор, а не попередній процесор оцінить sizeof, однак, все ще цікаво, чи є розумний спосіб цього досягти.)
Карл

@xdan Гарне рішення, на жаль, не задовольняє такі речі, як#define masks {0xff, 0xaf, 0x0f}
Саймон Баглі

59

Якщо ви використовуєте Visual C ++, ви можете використовувати #pragma message:

#include <boost/preprocessor/stringize.hpp>
#pragma message("BOOST_VERSION=" BOOST_PP_STRINGIZE(BOOST_VERSION))

Редагувати: Дякую LB за посилання

Мабуть, еквівалент GCC є (не перевірений):

#pragma message "BOOST_VERSION=" BOOST_PP_STRINGIZE(BOOST_VERSION)

5
Так називаються діагностичні прагми
LB40

4
Було б добре, якби ви включили визначення,BOOST_PP_STRINGIZE яке є приємним і коротким, і копіювати / вставляти.
Timmmm

Добре працює під gcc :)
Томас Легріс

14

Наскільки я знаю, що "#error" друкуватиме лише рядки, насправді вам навіть не потрібно використовувати лапки .

Ви намагалися написати різні цілеспрямовано неправильні коди, використовуючи "BOOST_VERSION"? Можливо, щось на кшталт "blah [BOOST_VERSION] = foo;" скаже вам щось на кшталт "string literal 1.2.1 не може використовуватися як адреса масиву". Це не буде симпатичним повідомленням про помилку, але, принаймні, воно покаже відповідне значення. Ви можете грати, поки не знайдете помилку компіляції, яка не підкаже вам значення.


Це не спрацювало, оскільки BOOST_VERSION є цілим числом, але я мусив це побачити з цим твердженням: std::vector<BOOST_VERSION>;в gcc 4.4.1. Дякую!
Jim Hunziker

Зауважте, що з Visual C ++ вам доведеться використовувати відповідь Бояна Ресніка.
Рафаель Сен-П'єр

Я намагався змусити це працювати, але повідомлення про помилку, яке GCC дав мені, було сумно нехарактерним. Але +1 за згадку про це.
Кріс Лутц

14

Без підвищення:

  1. визначте той самий макрос ще раз, і компілятор HIMSELF подасть попередження.

  2. З попередження ви бачите розташування попереднього визначення.

  3. vi файл попереднього визначення.

ambarish@axiom:~/cpp$ g++ shiftOper.cpp
shiftOper.cpp:7:1: warning: "LINUX_VERSION_CODE" redefined
shiftOper.cpp:6:1: warning: this is the location of the previous definition

#define LINUX_VERSION_CODE 265216
#define LINUX_VERSION_CODE 666

int main ()
{

}

Цей легший і простіший.
Tmx

1
сама : укладачі не мають статі
Sky

Це не працює з заздалегідь визначеними макросами, такими як __cplusplus.
ManuelAtWork

10

У Microsoft C / C ++ ви можете використовувати вбудований _CRT_STRINGIZE()для друку констант. Багато моїх stdafx.hфайлів містять певну комбінацію таких:

#pragma message("_MSC_VER      is " _CRT_STRINGIZE(_MSC_VER))
#pragma message("_MFC_VER      is " _CRT_STRINGIZE(_MFC_VER))
#pragma message("_ATL_VER      is " _CRT_STRINGIZE(_ATL_VER))
#pragma message("WINVER        is " _CRT_STRINGIZE(WINVER))
#pragma message("_WIN32_WINNT  is " _CRT_STRINGIZE(_WIN32_WINNT))
#pragma message("_WIN32_IE     is " _CRT_STRINGIZE(_WIN32_IE))
#pragma message("NTDDI_VERSION is " _CRT_STRINGIZE(NTDDI_VERSION)) 

і виводить щось подібне:

_MSC_VER      is 1915
_MFC_VER      is 0x0E00
_ATL_VER      is 0x0E00
WINVER        is 0x0600
_WIN32_WINNT  is 0x0600
_WIN32_IE     is 0x0700
NTDDI_VERSION is 0x06000000

5
#define a <::BOOST_VERSION>
#include a
MSVC2015 : фатальна помилка C1083: Не вдається відкрити включити файл: ':: 106200': Немає такого файлу чи каталогу

Працює, навіть якщо preprocess to fileце ввімкнено, навіть якщо присутні недійсні маркери:

#define a <::'*/`#>
#include a
MSVC2015 : фатальна помилка C1083: Не вдається відкрити включати файл: '::' * / `# ': Немає такого файлу чи каталогу
GCC4.x : попередження: відсутнє завершення' символ [-Winvalid-pp-token]
#define a <:: '* / `#>

Мій просто каже Build error: #include expects "FILENAME" or <FILENAME>. Зітхнути.
ендоліт

@endolith який компілятор і версія?
Андрій

DP8051 Keil 9.51 :)
ендоліт

@endolith Здається, цей компілятор дуже обмежений у попередній обробці : keil.com/support/man/docs/c51/c51_pp_directives.htm Але, на моєму боці, це майже працює так, як очікувалося, я просто видалив деякі недійсні символи, такі як ':*** WARNING C318 IN LINE 2 OF test.c: can't open file '::*/`'
Ендрі

Дякую, це врятувало мене, тому що повідомлення з прагмою не було реалізовано в компіляторі, який я використовував.
CodeMonkey

3

Ви також можете попередньо обробити вихідний файл і побачити, на що оцінюється значення препроцесора.


2

Ви шукаєте

#if BOOST_VERSION != "1.2"
#error "Bad version"
#endif

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


Я думаю, що подавець не хоче (просто) нав'язувати певне значення, вони хочуть побачити, що таке поточне значення.
KeyserSoze

Це єдине, що працює для мене. Я можу змінити #if VARIABLE == 123вислів на льоту, і підсвічування синтаксису підказує мені це значення, я думаю, воно є, чи ні ...
endolith

2

Дивлячись на вихід препроцесора, це найближче до відповіді, яку ви запитуєте.

Я знаю, що ви виключили це (та інші способи), але я не знаю чому. У вас є достатньо конкретна проблема, яку потрібно вирішити, але ви не пояснили, чому будь-який із "звичайних" методів не працює для вас.


Це, мабуть, правильна відповідь на загальну проблему.
jww

1

Ви можете написати програму, яка роздруковує BOOST_VERSIONта компілює та запускає її як частину вашої системи збирання. Інакше я думаю, що вам не пощастило.


Що стосується версії програмного забезпечення, визначеної в заголовку, ви, ймовірно, в безпеці (і це хороша відповідь). Але, як загальне рішення, можливим недоліком може стати тест-додаток і ваш реальний додаток, щоб вони мали однакове значення #define - залежно від їх включають шляхи, інші #defines, які можуть бути використані для встановлення значення цього , CFLAGS перейшов до компілятора тощо.
KeyserSoze

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

@swillden - ОП хотіла цього в час компіляції, а не під час виконання.
Кріс Лутц

Це також має тенденцію до розбиття побудови на основі крос-компілятора
Крейг Рінгер


1

Перегляньте також документацію Boost щодо того, як ви використовуєте макрос:

Відносно BOOST_VERSION, з http://www.boost.org/doc/libs/1_37_0/libs/config/doc/html/boost_config/boost_macro_reference.html#boost_config.boost_macro_reference.boost_helper_macros :

Описує номер прискореної версії у форматі XXYYZZ таким чином: що (BOOST_VERSION % 100)є другорядною версією, другорядною та основною.((BOOST_VERSION / 100) % 1000)(BOOST_VERSION / 100000)


0

Замість #error спробуйте змінити макрос безпосередньо перед його використанням. Компіляція не вдасться, і компілятор надасть поточне значення, яке, на його думку, стосується макроса.

#define BOOST_VERSION бла

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