#ifdef vs #if - o que é melhor / mais seguro como método para habilitar / desabilitar a compilação de seções específicas do código?

112

Isso pode ser uma questão de estilo, mas há um pouco de divisão em nossa equipe de desenvolvimento e eu me pergunto se mais alguém tem alguma idéia sobre o assunto ...

Basicamente, temos algumas instruções de impressão de depuração que desativamos durante o desenvolvimento normal. Pessoalmente, prefiro fazer o seguinte:

//---- SomeSourceFile.cpp ----

#define DEBUG_ENABLED (0)

...

SomeFunction()
{
    int someVariable = 5;

#if(DEBUG_ENABLED)
    printf("Debugging: someVariable == %d", someVariable);
#endif
}

Alguns membros da equipe preferem o seguinte:

// #define DEBUG_ENABLED

...

SomeFunction()
{
    int someVariable = 5;

#ifdef DEBUG_ENABLED
    printf("Debugging: someVariable == %d", someVariable);
#endif
}

... qual desses métodos soa melhor para você e por quê? Minha sensação é que o primeiro é mais seguro porque sempre há algo definido e não há perigo de que possa destruir outras definições em outro lugar.

Jon Cage
fonte
Observação: com #if, você também pode usar de #elifmaneira consistente, ao contrário de #ifdef. Assim, em vez de apenas usar #define BLAH, use #define BLAH 1com #if BLAH, etc ...
Andrew

Respostas:

82

Minha reação inicial foi #ifdef, é claro , mas acho que #ifrealmente tem algumas vantagens significativas para isso - aqui está o motivo:

Primeiro, você pode usar DEBUG_ENABLEDem testes de pré - processador e compilados. Exemplo - Freqüentemente, desejo tempos limite mais longos quando a depuração está habilitada, então usando #if, posso escrever isso

  DoSomethingSlowWithTimeout(DEBUG_ENABLED? 5000 : 1000);

... ao invés de ...

#ifdef DEBUG_MODE
  DoSomethingSlowWithTimeout(5000);
#else
  DoSomethingSlowWithTimeout(1000);
#endif

Em segundo lugar, você está em uma posição melhor se quiser migrar de uma #defineconstante para uma global. #defines geralmente são desaprovados pela maioria dos programadores C ++.

E, terceiro, você diz que tem uma divisão em sua equipe. Meu palpite é que isso significa que membros diferentes já adotaram abordagens diferentes e você precisa padronizar. Decidir que #ifé a escolha preferida significa que o código usando #ifdefirá compilar - e executar - mesmo quando DEBUG_ENABLEDfor falso. E é muito mais fácil rastrear e remover a saída de depuração produzida quando não deveria ser do que vice-versa.

Ah, e um pequeno ponto de legibilidade. Você deve ser capaz de usar verdadeiro / falso em vez de 0/1 no seu #definee, como o valor é um único token léxico, é o único momento em que você não precisa de parênteses em volta dele.

#define DEBUG_ENABLED true

ao invés de

#define DEBUG_ENABLED (1)
Roddy
fonte
A constante pode não ser usada para habilitar / desabilitar a depuração, então acionar um #ifdef com um #define para 0 pode não ser tão benigno. Quanto a verdadeiro / falso, foram adicionados em C99 e não existem em C89 / C90.
Michael Carman
Micheal: Ele / ela estava se defendendo contra o uso de #ifdef?!
Jon Cage
7
Sim, um problema #ifdefé que funciona com coisas que não estão definidas; se eles não foram definidos intencionalmente ou por causa de um erro de digitação ou o que quer que seja.
bames53
6
Sua adição à resposta está errada. #if DEBUG_ENBALEDnão é um erro detectado pelo pré-processador. Se DEBUG_ENBALEDnão for definido, ele se expande para o token 0nas #ifdiretivas.
R .. GitHub PARAR DE AJUDAR O ICE
6
@R .. Em muitos compiladores você pode habilitar um aviso para "#if DEBUG_ENABLED" quando DEBUG_ENABLED não está definido. No GCC, use "-Wundef". No Microsoft Visual Studio, use "/ w14668" para ativar C4668 como um aviso de nível 1.
Será
56

Ambos são horríveis. Em vez disso, faça o seguinte:

#ifdef DEBUG
#define D(x) do { x } while(0)
#else
#define D(x) do { } while(0)
#endif

Então, sempre que você precisar do código de depuração, coloque-o dentro D();. E seu programa não está poluído com horríveis labirintos de #ifdef.

R .. GitHub PARAR DE AJUDAR O GELO
fonte
6
@MatthieuM. Na verdade, acho que a versão original estava bem. O ponto-e-vírgula seria interpretado como uma declaração vazia. No entanto, esquecer o ponto-e-vírgula pode torná-lo perigoso.
Casey Kuball
31

#ifdef apenas verifica se um token está definido, dado

#define FOO 0

então

#ifdef FOO // is true
#if FOO // is false, because it evaluates to "#if 0"
Terence Simpson
fonte
20

Tivemos esse mesmo problema em vários arquivos e sempre há o problema de as pessoas se esquecerem de incluir um arquivo de "sinalizador de recursos" (com uma base de código de> 41.000 arquivos é fácil fazer).

Se você tivesse feature.h:

#ifndef FEATURE_H
#define FEATURE_H

// turn on cool new feature
#define COOL_FEATURE 1

#endif // FEATURE_H

Mas então você se esqueceu de incluir o arquivo de cabeçalho em file.cpp:

#if COOL_FEATURE
    // definitely awesome stuff here...
#endif

Então você tem um problema, o compilador interpreta COOL_FEATURE sendo indefinido como um "falso" neste caso e falha em incluir o código. Sim, o gcc suporta um sinalizador que causa um erro para macros indefinidas ... mas a maioria dos códigos de terceiros define ou não define recursos, portanto, não seria tão portátil.

Adotamos uma maneira portátil de corrigir este caso, bem como testar o estado de um recurso: macros de função.

se você alterou o feature.h acima para:

#ifndef FEATURE_H
#define FEATURE_H

// turn on cool new feature
#define COOL_FEATURE() 1

#endif // FEATURE_H

Mas, novamente, você se esqueceu de incluir o arquivo de cabeçalho em file.cpp:

#if COOL_FEATURE()
    // definitely awseome stuff here...
#endif

O pré-processador teria dado um erro devido ao uso de uma macro de função indefinida.

Brent Priddy
fonte
17

Para fins de compilação condicional, #if e #ifdef são quase iguais, mas não exatamente. Se sua compilação condicional depender de dois símbolos, #ifdef não funcionará bem. Por exemplo, suponha que você tenha dois símbolos de compilação condicional, PRO_VERSION e TRIAL_VERSION, você pode ter algo assim:

#if defined(PRO_VERSION) && !defined(TRIAL_VERSION)
...
#else
...
#endif

Usar #ifdef acima se torna muito mais complicado, especialmente fazer a parte #else funcionar.

Trabalho com código que usa extensivamente a compilação condicional e temos uma mistura de #if e #ifdef. Temos a tendência de usar # ifdef / # ifndef para o caso simples e #if sempre que dois ou mais símbolos estão sendo avaliados.

Mike Thompson
fonte
1
em #if definedque é defineduma palavra-chave ou?
nmxprime de
15

Acho que é inteiramente uma questão de estilo. Nenhum deles tem uma vantagem óbvia sobre o outro.

A consistência é mais importante do que qualquer escolha em particular, então eu recomendo que você se reúna com sua equipe e escolha um estilo, e se atenha a ele.

Derek Park
fonte
8

Eu mesmo prefiro:

#if defined(DEBUG_ENABLED)

Uma vez que torna mais fácil criar um código que procure a condição oposta, muito mais fácil de detectar:

#if !defined(DEBUG_ENABLED)

vs.

#ifndef(DEBUG_ENABLED)
Jim Buck
fonte
8
Pessoalmente, acho que é mais fácil perder aquele pequeno ponto de exclamação!
Jon Cage
6
Com destaque de sintaxe? :) No realce de sintaxe, o "n" em "ifndef" é muito mais difícil de detectar, pois é da mesma cor.
Jim Buck
Ok, eu quis dizer que #ifndef é mais fácil de detectar do que #if! Definido quando você está comparando com #if definido .. mas dado todos os #if definido / # if! Definido vs # ifdef / # ifndef, qualquer um é igualmente legível incorretamente!
Jon Cage
@JonCage Eu sei que já se passaram alguns anos desde este comentário, mas gostaria de salientar que você pode escrevê-lo #if ! definedpara tornar o !mais proeminente e difícil de perder.
Pharap
@Pharap - Isso certamente parece uma melhoria :)
Jon Cage
7

É uma questão de estilo. Mas recomendo uma maneira mais concisa de fazer isso:

#ifdef USE_DEBUG
#define debug_print printf
#else
#define debug_print
#endif

debug_print("i=%d\n", i);

Você faz isso uma vez, então sempre usa debug_print () para imprimir ou não fazer nada. (Sim, isso será compilado em ambos os casos.) Dessa forma, seu código não será distorcido com as diretivas do pré-processador.

Se você receber o aviso "a expressão não tem efeito" e quiser se livrar dela, aqui está uma alternativa:

void dummy(const char*, ...)
{}

#ifdef USE_DEBUG
#define debug_print printf
#else
#define debug_print dummy
#endif

debug_print("i=%d\n", i);
Lev
fonte
3
Talvez a macro de impressão não seja o melhor exemplo - na verdade, já fazemos isso em nossa base de código para nosso código de depuração mais padrão. Usamos os bits #if / #ifdefined para áreas nas quais você pode querer ativar a depuração extra.
Jon Cage
5

#ifoferece a opção de defini-lo como 0 para desligar a funcionalidade, enquanto ainda detecta que a chave está lá.
Pessoalmente, eu sempre #define DEBUG 1posso pegá-lo com um #if ou #ifdef

Martin Beckett
fonte
1
Isso falha, pois #define DEBUG = 0 agora não executará #if, mas executará #ifdef
tloach
1
Esse é o ponto, posso remover o DEBUG completamente ou apenas defini-lo como 0 para desativá-lo.
Martin Beckett
deveria ser #define DEBUG 1. Não#define DEBUG=1
Keshava GN
4

#if e #define MY_MACRO (0)

Usar #if significa que você criou uma macro "definir", ou seja, algo que será pesquisado no código para ser substituído por "(0)". Este é o "inferno da macro" que odeio ver em C ++, porque polui o código com possíveis modificações de código.

Por exemplo:

#define MY_MACRO (0)

int doSomething(int p_iValue)
{
   return p_iValue + 1 ;
}

int main(int argc, char **argv)
{
   int MY_MACRO = 25 ;
   doSomething(MY_MACRO) ;

   return 0;
}

dá o seguinte erro no g ++:

main.cpp|408|error: lvalue required as left operand of assignment|
||=== Build finished: 1 errors, 0 warnings ===|

Apenas um erro.

O que significa que sua macro interagiu com sucesso com seu código C ++: A chamada para a função foi bem-sucedida. Neste caso simples, é divertido. Mas minha própria experiência com macros brincando silenciosamente com meu código não é cheia de alegria e realização, então ...

#ifdef e #define MY_MACRO

Usar #ifdef significa que você "define" algo. Não que você dê um valor. Ainda é poluente, mas pelo menos será "substituído por nada" e não será visto pelo código C ++ como uma instrução de código lagitima. O mesmo código acima, com uma definição simples, ele:

#define MY_MACRO

int doSomething(int p_iValue)
{
   return p_iValue + 1 ;
}

int main(int argc, char **argv)
{
   int MY_MACRO = 25 ;
   doSomething(MY_MACRO) ;

   return 0;
}

Dá os seguintes avisos:

main.cpp||In function int main(int, char**)’:|
main.cpp|406|error: expected unqualified-id before ‘=’ token|
main.cpp|399|error: too few arguments to function int doSomething(int)’|
main.cpp|407|error: at this point in file|
||=== Build finished: 3 errors, 0 warnings ===|

Assim...

Conclusão

Prefiro viver sem macros em meu código, mas por vários motivos (definir protetores de cabeçalho ou macros de depuração), não posso.

Mas, pelo menos, gosto de torná-los o menos interativos possível com meu código C ++ legítimo. O que significa usar #define sem valor, usando #ifdef e #ifndef (ou mesmo #if definido como sugerido por Jim Buck) e, acima de tudo, dando-lhes nomes tão longos e tão estranhos que ninguém em sã consciência usará isso "por acaso", e de forma alguma afetará o código C ++ legítimo.

Post Scriptum

Agora, enquanto relendo meu post, me pergunto se não devo tentar encontrar algum valor que jamais será C ++ correto para adicionar à minha definição. Algo como

#define MY_MACRO @@@@@@@@@@@@@@@@@@

que poderia ser usado com #ifdef e #ifndef, mas não deixe o código compilar se usado dentro de uma função ... Eu tentei isso com sucesso no g ++ e deu o erro:

main.cpp|410|error: stray ‘@’ in program|

Interessante. :-)

paercebal
fonte
Eu concordo que macros podem ser perigosas, mas esse primeiro exemplo seria bastante óbvio para depurar e, claro, só dá um erro. Por que você esperaria mais? Eu vi erros muito mais desagradáveis ​​como resultado de macros ...
Jon Cage
É verdade que a diferença entre uma solução e outra é quase trivial. Mas, neste caso, como estamos falando de dois estilos de codificação concorrentes, então mesmo o trivial não pode ser ignorado, porque depois disso, tudo o que resta é o gosto pessoal (e nesse ponto, acredito que não deva ser normalizado )
paercebal
4

Isso não é uma questão de estilo. Além disso, a pergunta está infelizmente errada. Você não pode comparar essas diretivas de pré-processador no sentido de melhor ou mais seguro.

#ifdef macro

significa "se a macro for definida" ou "se a macro existir". O valor da macro não importa aqui. Pode ser qualquer coisa.

#if macro

se sempre comparar a um valor. No exemplo acima, é a comparação implícita padrão:

#if macro !=0

exemplo para o uso de #if

#if CFLAG_EDITION == 0
    return EDITION_FREE;
#elif CFLAG_EDITION == 1
    return EDITION_BASIC;
#else
    return EDITION_PRO;
#endif

agora você pode colocar a definição de CFLAG_EDITION em seu código

#define CFLAG_EDITION 1 

ou você pode definir a macro como sinalizador do compilador. Veja também aqui .

tmanthey
fonte
2

O primeiro parece mais claro para mim. Parece mais natural torná-lo um sinalizador em comparação com definido / não definido.

axblount
fonte
2

Ambos são exatamente equivalentes. No uso idiomático, #ifdef é usado apenas para verificar a definição (e o que eu usaria em seu exemplo), enquanto #if é usado em expressões mais complexas, como #if definido (A) &&! Definido (B).

zvrba
fonte
1
O OP não estava perguntando qual é melhor entre "#ifdef" e "#if definido", mas sim entre "# ifdef / # se definido" e "#if".
Shank
1

Um pouco OT, mas ligar / desligar o registro com o pré-processador é definitivamente abaixo do ideal em C ++. Existem boas ferramentas de registro, como o log4cxx do Apache, que são de código aberto e não restringem como você distribui seu aplicativo. Eles também permitem que você altere os níveis de registro sem recompilação, têm sobrecarga muito baixa se você desligar o registro e lhe dão a chance de desligar completamente o registro na produção.

David Nehme
fonte
1
Eu concordo, e nós realmente fazemos isso em nosso código, eu só queria um exemplo de algo que você pode usar #if etc. para
Jon Cage
1

Eu costumava usar #ifdef, mas quando mudei para Doxygen para documentação, descobri que macros comentadas não podem ser documentadas (ou, pelo menos, Doxygen produz um aviso). Isso significa que não posso documentar as macros de alternância de recursos que não estão ativadas no momento.

Embora seja possível definir as macros apenas para Doxygen, isso significa que as macros nas partes não ativas do código também serão documentadas. Pessoalmente, quero mostrar as opções de recurso e, caso contrário, apenas documentar o que está selecionado no momento. Além disso, torna o código bastante confuso se houver muitas macros que precisam ser definidas apenas quando o Doxygen processa o arquivo.

Portanto, neste caso, é melhor sempre definir as macros e usar #if.

StefanB
fonte
0

Sempre usei #ifdef e sinalizadores de compilador para defini-lo ...

aproximar
fonte
Algum motivo particular (por curiosidade)?
Jon Cage
2
Para ser honesto, nunca pensei sobre isso - apenas como tem sido feito nos lugares em que trabalhei. Ele dá a vantagem de que, em vez de fazer uma mudança de código para uma construção de produção, tudo o que você precisa fazer é 'make DEBUG' para depuração ou 'make PRODUCTION' para regular
abordagem
0

Como alternativa, você pode declarar uma constante global e usar o C ++ if, em vez do pré-processador #if. O compilador deve otimizar os branches não usados ​​para você, e seu código ficará mais limpo.

Aqui está o que C ++ Gotchas de Stephen C. Dewhurst diz sobre o uso de # if's.

Dima
fonte
1
Essa é uma solução péssima, ela tem os seguintes problemas: 1. Funciona apenas em funções, você não pode remover variáveis ​​de classe desnecessárias, etc. 2. Compiladores podem lançar avisos sobre código inacessível 3. Código em if ainda precisa ser compilado, o que significa você tem que manter todas as funções de depuração definidas, etc.
Don Neufeld,
Primeiro, a questão era especificamente sobre depurar printfs, portanto, variáveis ​​de classe desnecessárias não são um problema aqui. Segundo, dadas as capacidades dos compiladores modernos, você deve usar #ifdefs o mínimo possível. Na maioria dos casos, você pode usar configurações de compilação ou especializações de modelo.
Dima,
0

Há uma diferença no caso de uma maneira diferente de especificar uma definição condicional para o driver:

diff <( echo | g++ -DA= -dM -E - ) <( echo | g++ -DA -dM -E - )

resultado:

344c344
< #define A 
---
> #define A 1

Isso significa que -DAé sinônimo de -DA=1e se o valor for omitido, pode haver problemas no caso de #if Auso.

Tomilov Anatoliy
fonte
0

Gosto #define DEBUG_ENABLED (0)quando você deseja vários níveis de depuração. Por exemplo:

#define DEBUG_RELEASE (0)
#define DEBUG_ERROR (1)
#define DEBUG_WARN (2)
#define DEBUG_MEM (3)
#ifndef DEBUG_LEVEL
#define DEBUG_LEVEL (DEBUG_RELEASE)
#endif
//...

//now not only
#if (DEBUG_LEVEL)
//...
#endif

//but also
#if (DEBUG_LEVEL >= DEBUG_MEM)
LOG("malloc'd %d bytes at %s:%d\n", size, __FILE__, __LINE__);
#endif

Torna mais fácil depurar vazamentos de memória, sem ter todas essas linhas de log em sua maneira de depurar outras coisas.

Além disso, a #ifndefdefinição ao redor torna mais fácil escolher um nível de depuração específico na linha de comando:

make -DDEBUG_LEVEL=2
cmake -DDEBUG_LEVEL=2
etc

Se não fosse por isso, eu daria vantagem #ifdefporque o sinalizador compilador / make seria substituído pelo sinalizador do arquivo. Então você não precisa se preocupar em mudar de volta o cabeçalho antes de fazer o commit.

memtha
fonte