Jak wyświetlić wartość #define w czasie kompilacji?

124

Próbuję dowiedzieć się, jakiej wersji Boost mój kod używa. Chcę zrobić coś takiego:

#error BOOST_VERSION

ale preprocesor nie rozwija BOOST_VERSION.

Wiem, że mógłbym wydrukować go w czasie wykonywania z programu i wiem, że mógłbym spojrzeć na dane wyjściowe preprocesora, aby znaleźć odpowiedź. Czuję, że sposób na zrobienie tego podczas kompilacji może być przydatny.

Jim Hunziker
źródło
7
Dla przyszłych gości ... Chris Barry zapewnia na końcu uogólnione rozwiązanie (pozbawione specyficznych elementów Boost).
jww

Odpowiedzi:

118

Wiem, że minęło dużo czasu od pierwotnego zapytania, ale może to być przydatne.

Można to zrobić w GCC za pomocą operatora stringify „#”, ale wymaga to dwóch etapów.

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

Wartość makra można następnie wyświetlić za pomocą:

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

Zobacz: 3.4 Stringifikacja w dokumentacji online gcc.

Jak to działa:

Preprocesor rozumie cytowane ciągi i traktuje je inaczej niż zwykły tekst. Przykładem tego specjalnego traktowania jest konkatenacja ciągów. Komunikat pragma wymaga argumentu będącego łańcuchem cudzysłowu. Jeśli argument zawiera więcej niż jeden składnik, wszystkie muszą być łańcuchami, aby można było zastosować konkatenację ciągów. Preprocesor nigdy nie może założyć, że niecytowany ciąg powinien być traktowany tak, jakby był cytowany. Jeśli tak się stało:

#define ABC 123
int n = ABC;

nie będzie się kompilować.

Rozważmy teraz:

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

co jest równoważne

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

Powoduje to ostrzeżenie preprocesora, ponieważ abc (bez cudzysłowu) nie może zostać połączone z poprzednim ciągiem.

Rozważmy teraz preprocesor stringize (który kiedyś był nazywany stringification, linki w dokumentacji zostały zmienione, aby odzwierciedlić poprawioną terminologię. (Nawiasem mówiąc, oba terminy są równie obrzydliwe. Prawidłowy termin to oczywiście stringifaction. Przygotuj się do aktualizacji Twoje linki.)). Działa to tylko na argumentach makra i zastępuje nierozwinięty argument argumentem ujęty w podwójne cudzysłowy. A zatem:

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

przypisze identyczne wartości s1 i s2. Jeśli uruchomisz gcc -E, zobaczysz to na wyjściu. Być może STR byłoby lepiej nazwane czymś w rodzaju ENQUOTE.

To rozwiązuje problem umieszczania cudzysłowów wokół niecytowanego elementu, problem polega teraz na tym, że jeśli argumentem jest makro, makro nie zostanie rozwinięte. Dlatego potrzebne jest drugie makro. XSTR rozszerza swój argument, a następnie wywołuje STR, aby umieścić rozwiniętą wartość w cudzysłowie.

Chris Barry
źródło
3
Ciekaw
4
@VincentFourmond Bez etapu XSTR makro nie jest rozwijane. Więc jeśli # zdefiniowałeś ABC 42 \ n STR (ABC), otrzymasz „ABC”. Zobacz gcc.gnu.org/onlinedocs/cpp/Stringification.html
rob05c
Działa to również świetnie z Xcode 8, np. Zamiana ABC na __IPHONE_9_3.
funroll
Wydaje się, że zmieniła się terminologia GCC, a wraz z nią adres URL, który jest teraz https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html#Stringizing
Chris Barry
119

BOOST_PP_STRINGIZE wydaje się być doskonałym rozwiązaniem dla C ++, ale nie dla zwykłego C.

Oto moje rozwiązanie dla CPP GNU:

/* 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))

Powyższe definicje skutkują:

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"

W przypadku zmiennych „zdefiniowane jako interger” , „zdefiniowane jako łańcuch” i „zdefiniowane, ale bez wartości” , działają one dobrze. Tylko dla zmiennej „niezdefiniowanej” wyświetlały się dokładnie tak samo, jak oryginalna nazwa zmiennej. Trzeba się do tego przyzwyczaić - a może ktoś zaproponuje lepsze rozwiązanie.

Jackie Yeh
źródło
doskonały! Jakieś doświadczenia z ARM RVCT? wygląda na to, że nie ma funkcji „Stringification”, jak GCC infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0491c/ ...
xdan
2
Świetne rozwiązanie. Jeśli jednak chcę wyświetlić rozmiar wartości obliczonej w czasie kompilacji, np. Rozmiar złożonej struktury, czy można to zrobić? Metoda sugerowana w tej odpowiedzi wydaje się generować DEFINED_INT=(sizeof(MY_STRUCT))bez oceny sizeofoperatora.
Carl
(Dodanie komentarza: nie jest to nieoczekiwane, ponieważ to kompilator, a nie preprocesor, oceni sizeof, jednak nadal jest ciekawy, czy istnieje sprytny sposób na osiągnięcie tego.)
Carl
@xdan Dobre rozwiązanie, niestety nie obsługuje takich rzeczy jak#define masks {0xff, 0xaf, 0x0f}
Simon Bagley
59

Jeśli używasz języka Visual C ++, możesz użyć #pragma message:

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

Edycja: dzięki LB za link

Najwyraźniej odpowiednik GCC to (nie testowano):

#pragma message "BOOST_VERSION=" BOOST_PP_STRINGIZE(BOOST_VERSION)
Bojan Resnik
źródło
5
To się nazywa pragma diagnostyczne, gcc.gnu.org/onlinedocs/gcc/...
LB40
4
Byłoby miło, gdybyś dołączył definicję,BOOST_PP_STRINGIZE która jest ładna i krótka i można ją skopiować / wkleić.
Timmmm
Działa dobrze pod gcc :)
Thomas Legris
14

O ile wiem, '#error' wypisze tylko ciągi znaków, w rzeczywistości nie musisz nawet używać cudzysłowów .

Czy próbowałeś napisać różne celowo niepoprawne kody przy użyciu „BOOST_VERSION”? Może coś w rodzaju „bla [BOOST_VERSION] = foo;” powie ci coś w rodzaju „literał ciągu 1.2.1 nie może być użyty jako adres tablicy”. Nie będzie to ładny komunikat o błędzie, ale przynajmniej pokaże odpowiednią wartość. Możesz bawić się, aż znajdziesz błąd kompilacji, który mówi ci o wartości.

KeyserSoze
źródło
To nie zadziałało, ponieważ BOOST_VERSION jest liczbą całkowitą, ale zobaczyłem to za pomocą tego stwierdzenia: std::vector<BOOST_VERSION>;w gcc 4.4.1. Dzięki!
Jim Hunziker
Zauważ, że w przypadku Visual C ++ musiałbyś użyć odpowiedzi Bojana Resnika.
Raphaël Saint-Pierre
Próbowałem, aby to działało, ale komunikat o błędzie, który dał mi GCC, był niestety nieopisowy. Ale +1 za wspomnienie o tym.
Chris Lutz
14

Bez doładowania:

  1. zdefiniuj ponownie to samo makro, a sam kompilator wyświetli ostrzeżenie.

  2. Z ostrzeżenia można zobaczyć lokalizację poprzedniej definicji.

  3. vi o poprzedniej definicji.

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

}
Ambarish Kumar Shivam
źródło
Ten jest łatwiejszy i nieskomplikowany.
Tmx
1
sama w sobie : kompilatory nie mają płci
Sky
Nie działa to w przypadku wstępnie zdefiniowanych makr, takich jak __cplusplus.
ManuelAtWork
10

W Microsoft C / C ++ możesz użyć wbudowanego _CRT_STRINGIZE()do drukowania stałych. Wiele moich stdafx.hplików zawiera kombinacje następujących elementów:

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

i wyświetla coś takiego:

_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
źródło
5
#define a <::BOOST_VERSION>
#include a
MSVC2015 : błąd krytyczny C1083: nie można otworzyć pliku dołączanego: „:: 106200”: nie ma takiego pliku lub katalogu

Działa, nawet jeśli preprocess to filejest włączona, nawet jeśli obecne są nieprawidłowe tokeny:

#define a <::'*/`#>
#include a
MSVC2015 : błąd krytyczny C1083: Nie można otworzyć pliku dołączanego: '::' * / `# ': Nie ma takiego pliku lub katalogu
GCC4.x : ostrzeżenie: brak znaku kończącego' [-Winvalid-pp-token]
#define a <:: '* / `#>
Andry
źródło
Mój tylko mówi Build error: #include expects "FILENAME" or <FILENAME>. Westchnienie.
endolit
@endolith jaki kompilator i wersję?
Andry,
DP8051 Keil 9,51 :)
endolit
@endolith Wygląda na to, że ten kompilator jest bardzo ograniczony w przetwarzaniu wstępnym : keil.com/support/man/docs/c51/c51_pp_directives.htm Ale po mojej stronie działa prawie zgodnie z oczekiwaniami, właśnie usunąłem niektóre nieprawidłowe znaki, takie jak ':*** WARNING C318 IN LINE 2 OF test.c: can't open file '::*/`'
Andry
Dziękuję, to mnie uratowało, ponieważ treść komunikatu pragmy nie została zaimplementowana w kompilatorze, którego używałem.
CodeMonkey
3

Możesz również wstępnie przetworzyć plik źródłowy i zobaczyć, do czego szacowana jest wartość preprocesora.

fbrereto
źródło
2

Czy szukasz

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

Niedobrze, jeśli BOOST_VERSION jest ciągiem, jak założyłem, ale mogą również istnieć indywidualne liczby całkowite zdefiniowane dla numerów głównych, pomocniczych i numerów wersji.

user47559
źródło
Myślę, że przesyłający nie chce (po prostu) wymusić określonej wartości, chce zobaczyć, jaka jest bieżąca wartość.
KeyserSoze
To jedyna rzecz, która działa dla mnie. Mogę zmienić #if VARIABLE == 123instrukcję w locie, a podświetlanie składni mówi mi, czy jest to wartość, którą myślę, czy nie ...
endolith
2

Spojrzenie na dane wyjściowe preprocesora jest najbliższe odpowiedzi, o którą prosisz.

Wiem, że wykluczyłeś to (i inne sposoby), ale nie jestem pewien, dlaczego. Masz wystarczająco konkretny problem do rozwiązania, ale nie wyjaśniłeś, dlaczego żadna z „normalnych” metod nie działa dobrze.

dwc
źródło
To prawdopodobnie poprawna odpowiedź na ogólny problem.
jww
1

Możesz napisać program, który wydrukuje, BOOST_VERSIONskompiluje i uruchomi go jako część twojego systemu budowania. W przeciwnym razie myślę, że nie masz szczęścia.

Chris Lutz
źródło
W przypadku wersji oprogramowania zdefiniowanej w nagłówku prawdopodobnie jesteś bezpieczny (i to dobra odpowiedź). Ale jako rozwiązanie ogólne, możliwym minusem byłoby uzyskanie tej samej wartości #define - w zależności od ich ścieżek dołączania, innych #defines, które mogą być użyte do ustawienia tej wartości. , CFLAGS przekazane do kompilatora itp.
KeyserSoze
Wydrukuj go ze swojego prawdziwego programu. Jeśli jest graficzny, umieść go w oknie dialogowym „informacje”. Jeśli wiersz poleceń, ustaw go jako opcję (być może część --version). Jeśli jest to demon, zapisz go w pliku dziennika. Jeśli jest osadzony, znajdź inny sposób.
divegeek
@swillden - OP chciał tego w czasie kompilacji, a nie w czasie wykonywania.
Chris Lutz
To również zwykle psuje kompilacje oparte na cross-kompilatorach
Craig Ringer
1

BOOST_VERSION jest zdefiniowana w pliku nagłówka boost version.hpp.

David Harris
źródło
0

Zamiast #error spróbuj przedefiniować makro tuż przed jego użyciem. Kompilacja zakończy się niepowodzeniem, a kompilator poda bieżącą wartość, którą według niego ma zastosować do makra.

# zdefiniować BOOST_VERSION bla

tecMav
źródło