Как мне показать значение #define во время компиляции?

124

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

#error BOOST_VERSION

но препроцессор не расширяет BOOST_VERSION.

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

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

Ответы:

118

Я знаю, что прошло много времени после исходного запроса, но это все еще может быть полезно.

Это можно сделать в GCC с помощью оператора строкового преобразования "#", но это требует двух этапов.

#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 (без кавычек) не может быть объединено с предыдущей строкой.

Теперь рассмотрим преобразование препроцессора в строку (которое когда-то называлось преобразованием в строку, ссылки в документации были изменены, чтобы отразить пересмотренную терминологию. (Оба термина, кстати, одинаково отвратительны. Правильный термин, конечно, stringifaction. Будьте готовы обновить ваши ссылки.)) оператор. Это действует только на аргументы макроса и заменяет нерасширенный аргумент аргументом, заключенным в двойные кавычки. Таким образом:

#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"

Для переменных «определено как число» , «определено как строка» и «определено, но без значения» они работают нормально. Только для «не определенной» переменной они отображались точно так же, как исходное имя переменной. Вы должны привыкнуть к этому - или, может быть, кто-то может предложить лучшее решение.

Джеки Йе
источник
превосходно! Есть опыт работы с ARM RVCT? похоже, что у GCC нет функции "Stringification", поскольку GCC infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0491c/…
xdan
2
Отличное решение. Однако, если я хочу отобразить размер вычисленного значения времени компиляции, например, размер сложной структуры, можно ли это сделать? Кажется, что метод, предложенный в этом ответе, генерируется DEFINED_INT=(sizeof(MY_STRUCT))без sizeofоценки оператора.
Carl
(Добавление комментария: не неожиданно, поскольку оценку будет проводить компилятор, а не препроцессор 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
Это называется диагностическими прагмами, gcc.gnu.org/onlinedocs/gcc/…
LB40
4
Было бы неплохо, если бы вы включили определение того,BOOST_PP_STRINGIZE что красиво, коротко и копировать / вставлять.
Timmmm 03
Прекрасно работает под gcc :)
Томас
14

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

Вы пробовали писать различный целенаправленно неправильный код с помощью «BOOST_VERSION»? Возможно, что-то вроде "blah [BOOST_VERSION] = foo;" сообщит вам что-то вроде «строковый литерал 1.2.1 не может использоваться как адрес массива». Это не будет красивым сообщением об ошибке, но, по крайней мере, оно покажет вам соответствующее значение. Вы можете поиграть, пока не найдете ошибку компиляции, которая действительно сообщает вам значение.

KeyserSoze
источник
Это не сработало, поскольку BOOST_VERSION - целое число, но я увидел это с помощью этого оператора: std::vector<BOOST_VERSION>;в gcc 4.4.1. Спасибо!
Джим Ханзикер,
Обратите внимание, что с Visual C ++ вам придется использовать ответ Бояна Резника.
Raphaël Saint-Pierre,
Я пытался заставить это работать, но сообщение об ошибке, которое дал мне GCC, было, к сожалению, не описательным. Но +1 за упоминание об этом.
Крис Лутц,
14

Без наддува:

  1. определите тот же макрос снова, и компилятор САМ выдаст предупреждение.

  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 08
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
UweBaemayr
источник
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 <:: «* / '#>
Andry
источник
Моя просто говорит Build error: #include expects "FILENAME" or <FILENAME>. Вздох.
endolith
@endolith какой компилятор и версия?
Andry
DP8051 Keil 9.51 :)
endolith
@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

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

fbrereto
источник
2

Ты ищешь

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

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

user47559
источник
Я думаю, что отправитель не хочет (просто) применять конкретное значение, они хотят видеть, каково текущее значение.
KeyserSoze,
Это единственное, что у меня работает. Я могу изменить это #if VARIABLE == 123выражение на лету, и подсветка синтаксиса скажет мне, является ли это значение, которое я думаю, или нет ...
Эндолит
2

Результат препроцессора - это наиболее близкий ответ к вашему запросу.

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

DWC
источник
Вероятно, это правильный ответ на общую проблему.
jww
1

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

Крис Лутц
источник
В случае версии программного обеспечения, определенной в заголовке, вы, вероятно, в безопасности (и это хороший ответ). Но в качестве общего решения возможный недостаток будет заключаться в том, что ваше тестовое приложение и ваше реальное приложение будут иметь одинаковое значение #define - в зависимости от их путей включения, другие #defines, которые могут использоваться для установки значения этого , CFLAGS переданы компилятору и т. д.
KeyserSoze
Распечатайте это из своей реальной программы. Если графический, поместите его в диалоговое окно «О нас». Если используется командная строка, сделайте это опцией (возможно, часть --version). Если это демон, запишите его в файл журнала. Если встроено, найдите другой способ.
divegeek
@swillden - OP хотел его во время компиляции, а не во время выполнения.
Крис Лутц,
Это также имеет тенденцию нарушать сборки на основе кросс-компилятора
Крейг Рингер,
1

BOOST_VERSION определяется в файле заголовка boost version.hpp.

Дэвид Харрис
источник
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 blah

tecMav
источник