Como faço para mostrar o valor de um #define em tempo de compilação?

123

Estou tentando descobrir qual versão do Boost meu código pensa que está usando. Eu quero fazer algo assim:

#error BOOST_VERSION

mas o pré-processador não expande BOOST_VERSION.

Eu sei que poderia imprimi-lo em tempo de execução a partir do programa e sei que poderia olhar a saída do pré-processador para encontrar a resposta. Acho que ter uma maneira de fazer isso durante a compilação pode ser útil.

Jim Hunziker
fonte
7
Para futuros visitantes ... Chris Barry fornece a solução generalizada no final (sem o material específico do Boost).
jww

Respostas:

117

Eu sei que isso é muito tempo depois da consulta original, mas ainda pode ser útil.

Isso pode ser feito no GCC usando o operador stringify "#", mas requer dois estágios.

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

O valor de uma macro pode então ser exibido com:

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

Veja: 3.4 Stringificação na documentação online do gcc.

Como funciona:

O pré-processador entende strings entre aspas e as trata de maneira diferente do texto normal. A concatenação de strings é um exemplo desse tratamento especial. O pragma da mensagem requer um argumento que é uma string entre aspas. Quando há mais de um componente para o argumento, todos eles devem ser strings para que a concatenação de strings possa ser aplicada. O pré-processador nunca pode assumir que uma string sem aspas deva ser tratada como se estivesse entre aspas. Se sim, então:

#define ABC 123
int n = ABC;

não compilaria.

Agora considere:

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

que é equivalente a

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

Isso causa um aviso do pré-processador porque abc (sem aspas) não pode ser concatenado com a string anterior.

Agora considere o pré-processador stringize (que já foi chamado de stringification, os links na documentação foram alterados para refletir a terminologia revisada. (Ambos os termos, aliás, são igualmente detestáveis. O termo correto é, obviamente, stringifaction. Esteja pronto para atualizar seus links.)) operador. Isso atua apenas nos argumentos de uma macro e substitui o argumento não expandido pelo argumento entre aspas duplas. Portanto:

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

irá atribuir valores idênticos a s1 e s2. Se você executar gcc -E, poderá ver isso na saída. Talvez STR fosse melhor denominado algo como ENQUOTE.

Isso resolve o problema de colocar aspas em torno de um item não cotado, o problema agora é que, se o argumento for uma macro, a macro não será expandida. É por isso que a segunda macro é necessária. O XSTR expande seu argumento e chama STR para colocar o valor expandido entre aspas.

Chris Barry
fonte
3
Estou curioso para
saber
4
@VincentFourmond Sem o estágio XSTR, a macro não é expandida. Portanto, se você definisse #define ABC 42 \ n STR (ABC), obteria "ABC". Consulte gcc.gnu.org/onlinedocs/cpp/Stringification.html
rob05c
Isso também funciona muito bem com o Xcode 8, por exemplo, substituindo ABC por __IPHONE_9_3.
funroll de
A terminologia do GCC parece ter mudado, e com ela o URL, que agora é https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html#Stringizing
Chris Barry
119

BOOST_PP_STRINGIZE parece uma solução excelente para C ++, mas não para C.

Aqui está minha solução para 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))

As definições acima resultam em:

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"

Para variáveis "definidas como interger" , "definidas como string" e "definidas mas sem valor" , elas funcionam muito bem. Apenas para a variável "não definida" , eles exibiram exatamente o mesmo que o nome da variável original. Você tem que se acostumar - ou talvez alguém possa fornecer uma solução melhor.

Jackie Yeh
fonte
excelente! Alguma experiência no ARM RVCT? parece que não tem o recurso "Stringification" como GCC infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0491c/…
xdan
2
Ótima solução. No entanto, se eu desejar exibir o tamanho de um valor calculado em tempo de compilação, por exemplo, o tamanho de uma estrutura complexa, isso pode ser feito? O método sugerido nesta resposta parece gerar DEFINED_INT=(sizeof(MY_STRUCT)), sem que o sizeofoperador seja avaliado.
Carl
(Comentário adicional: não inesperado, já que é o compilador, e não o pré-processador, que avaliará sizeof, no entanto, ainda curioso se há uma maneira inteligente de fazer isso.)
Carl
@xdan Boa solução, infelizmente não serve para coisas como#define masks {0xff, 0xaf, 0x0f}
Simon Bagley
59

Se estiver usando Visual C ++, você pode usar #pragma message:

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

Edit: Obrigado a LB pelo link

Aparentemente, o equivalente GCC é (não testado):

#pragma message "BOOST_VERSION=" BOOST_PP_STRINGIZE(BOOST_VERSION)
Bojan Resnik
fonte
5
Isso é chamado de pragmas diagnósticos, gcc.gnu.org/onlinedocs/gcc/…
LB40 de
4
Seria bom se você incluísse a definição deBOOST_PP_STRINGIZE que é bom e curto e pode ser copiado / colado.
Timmmm
Funciona bem no gcc :)
Thomas Legris
14

Pelo que eu sei, '#error' apenas imprimirá strings, na verdade você nem precisa usar aspas .

Você já tentou escrever vários códigos propositalmente incorretos usando "BOOST_VERSION"? Talvez algo como "blá [BOOST_VERSION] = foo;" dirá algo como "string literal 1.2.1 não pode ser usado como endereço de array". Não será uma mensagem de erro bonita, mas pelo menos mostrará o valor relevante. Você pode brincar até encontrar um erro de compilação que informe o valor.

KeyserSoze
fonte
Isso não funcionou, já que BOOST_VERSION é um número inteiro, mas consegui ver com esta declaração: std::vector<BOOST_VERSION>;no gcc 4.4.1. Obrigado!
Jim Hunziker
Observe que, com o Visual C ++, você teria que usar a resposta de Bojan Resnik.
Raphaël Saint-Pierre,
Tentei fazer isso funcionar, mas a mensagem de erro que o GCC me deu era infelizmente não descritiva. Mas 1 por mencioná-lo.
Chris Lutz,
14

Sem impulso:

  1. defina a mesma macro novamente e o compilador HIMSELF dará um aviso.

  2. A partir do aviso, você pode ver a localização da definição anterior.

  3. arquivo vi de definição anterior.

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
fonte
Este é mais fácil e direto.
Tmx
1
em si : compiladores não têm gênero
Sky
Isso não funciona com macros predefinidas, como __cplusplus.
ManuelAtWork
10

No Microsoft C / C ++, você pode usar o interno _CRT_STRINGIZE()para imprimir constantes. Muitos dos meus stdafx.harquivos contêm alguma combinação destes:

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

e produz algo como este:

_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
fonte
5
#define a <::BOOST_VERSION>
#include a
MSVC2015 : erro fatal C1083: Não é possível abrir arquivo de inclusão: ':: 106200': Não existe esse arquivo ou diretório

Funciona mesmo se preprocess to fileestiver ativado, mesmo se tokens inválidos estiverem presentes:

#define a <::'*/`#>
#include a
MSVC2015 : erro fatal C1083: Não é possível abrir arquivo de inclusão: '::' * / `# ': Não
existe esse arquivo ou diretório GCC4.x : aviso: falta de terminação' caractere [-Winvalid-pp-token]
#define a <:: '* / `#>
Andry
fonte
O meu apenas diz Build error: #include expects "FILENAME" or <FILENAME>. Suspiro.
endolith
@endolith qual compilador e versão?
Andry
DP8051 Keil 9.51 :)
endólito de
@endolith Parece que este compilador é muito limitado no pré-processamento: keil.com/support/man/docs/c51/c51_pp_directives.htm Mas, no meu lado, quase funciona como esperado, acabei de remover alguns caracteres inválidos como ':*** WARNING C318 IN LINE 2 OF test.c: can't open file '::*/`'
Andry
Obrigado, isso me salvou porque o material da mensagem pragma não foi implementado no compilador que eu estava usando.
CodeMonkey
3

Você também pode pré-processar o arquivo de origem e ver como o valor do pré-processador é avaliado.

Fbrereto
fonte
2

Você está procurando

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

Não é ótimo se BOOST_VERSION for uma string, como assumi, mas também pode haver inteiros individuais definidos para os números principais, secundários e de revisão.

user47559
fonte
Acho que o remetente não deseja (apenas) impor um valor específico, ele deseja ver qual é o valor atual.
KeyserSoze
Esta é a única coisa que funciona para mim. Posso alterar a #if VARIABLE == 123instrução imediatamente e o destaque de sintaxe me diz se é o valor que eu acho que é ou não ...
endolith
2

Olhar para a saída do pré-processador é a coisa mais próxima da resposta que você pede.

Eu sei que você excluiu isso (e outras formas), mas não tenho certeza do porquê. Você tem um problema específico o suficiente para resolver, mas não explicou por que algum dos métodos "normais" não funciona bem para você.

dwc
fonte
Esta é provavelmente a resposta correta para o problema geral.
jww
1

Você pode escrever um programa que imprima, BOOST_VERSIONcompile e execute-o como parte de seu sistema de construção. Caso contrário, acho que você está sem sorte.

Chris Lutz
fonte
Para o caso de uma versão de software definida em um cabeçalho, você provavelmente está seguro (e é uma boa resposta). Mas como uma solução geral, uma possível desvantagem seria fazer com que seu aplicativo de teste e seu aplicativo real tivessem o mesmo valor de #define - dependendo de seus caminhos de inclusão, outros #defines que podem ser usados ​​para definir o valor daquele , os CFLAGS passados ​​para o compilador etc.
KeyserSoze
Imprima a partir do seu programa real. Se for gráfico, coloque-o na caixa de diálogo "sobre". Se for linha de comando, torne-o uma opção (parte de --version, talvez). Se for um daemon, grave-o em um arquivo de log. Se embutido, encontre outra maneira.
divegeek de
@swillden - O OP queria isso em tempo de compilação, não em tempo de execução.
Chris Lutz de
Isso também tende a interromper as compilações baseadas em compilador cruzado
Craig Ringer
1

BOOST_VERSION é definido no arquivo de cabeçalho boost version.hpp.

David Harris
fonte
1

Dê uma olhada na documentação do Boost também, sobre como você está usando a macro:

Em referência a BOOST_VERSION, de 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 :

Descreve o número da versão ampliada no formato XXYYZZ de modo que: (BOOST_VERSION % 100)é a versão subsecundária , é a versão secundária e é a versão principal.((BOOST_VERSION / 100) % 1000)(BOOST_VERSION / 100000)

bn.
fonte
0

Em vez de #error, tente redefinir a macro, pouco antes de ser usada. A compilação falhará e o compilador fornecerá o valor atual que ele pensa que se aplica à macro.

#define BOOST_VERSION blah

tecMav
fonte