É melhor usar a diretiva pré-processador ou a declaração if (constante)?

10

Digamos que tenhamos uma base de código usada para muitos clientes diferentes e que tenhamos algum código relevante apenas para clientes do tipo X. É melhor usar diretivas de pré-processador para incluir esse código apenas no cliente do tipo X ou usar instruções if? Para ser mais claro:

// some code
#if TYPE_X_COSTUMER  = 1
// do some things
#endif
// rest of the code

ou

if(TYPE_X_COSTUMER) {
    // do some things
}

Os argumentos em que posso pensar são:

  • A diretiva pré-processador resulta em menor presença de código e menos ramificações (em compiladores que não otimizam)
  • Se as instruções resultarem em código que sempre compila, por exemplo, se alguém cometer um erro que prejudique o código irrelevante para o projeto em que trabalha, o erro ainda aparecerá e ele não corromperá a base de código. Caso contrário, ele não estará ciente da corrupção.
  • Sempre me disseram para preferir o uso do processador ao uso do pré-processador (se esse é um argumento) ...

O que é preferível - quando se fala de uma base de código para muitos clientes diferentes?

MByD
fonte
3
Quais são as chances de você enviar uma compilação não criada usando um compilador de otimização? E se isso acontecer, o impacto será importante (especialmente quando contextualizado com todas as outras otimizações que você sentirá falta)?
@ delnan - O código é usado para muitas plataformas diferentes, com muitos compiladores diferentes. E em relação ao impacto - pode, em certos casos.
MByD 24/11/11
Disseram-lhe para preferir algo? É como forçar alguém a comer comida do KFC, enquanto ele não gosta de frango.
rightfold
@WTP - não, eu não estava, estou interessado em saber mais, para tomar melhores decisões no futuro.
MByD 24/11/11
No seu segundo exemplo, TYPE_X_CUSTOMERainda é uma macro de pré-processador?
detly

Respostas:

5

Eu acho que há uma vantagem de usar um #define que você não mencionou, e esse é o fato de que você pode definir o valor na linha de comando (portanto, defina-o a partir do script de construção de uma etapa).

Fora isso, geralmente é melhor evitar macros. Eles não respeitam o escopo e isso pode causar problemas. Somente compiladores muito burros não podem otimizar uma condição com base em uma constante em tempo de compilação. Não sei se isso é uma preocupação para o seu produto (por exemplo, pode ser importante manter o código pequeno em plataformas incorporadas).

Tamás Szelei
fonte
Na verdade, isso é para sistemas embarcados
MByD 24/11
O problema é que os sistemas embarcados geralmente usam algum compilador antigo (como, por exemplo, o gcc 2.95).
BЈовић
@VJo - GCC é o caso de sorte :)
MByD
Experimente então. Se incluir o código não utilizado e esse for um tamanho considerável, vá para o define.
Tamás Szelei
Se você quiser defini-lo por meio da linha de comando do compilador, ainda utilizaria uma constante no próprio código e apenas #define o valor atribuído a essa constante.
CodesInChaos
12

Quanto a muitas perguntas, a resposta desta pergunta é que depende . Em vez de dizer o que é melhor , dei exemplos e objetivos em que um é melhor que outro.

Tanto o pré-processador quanto a constante têm seus próprios locais de uso apropriado.

No caso do pré-processador, o código é removido antes do tempo de compilação. Portanto, é mais adequado para situações em que o código não deve ser compilado . Isso pode afetar a estrutura do módulo, as dependências e pode permitir a seleção dos melhores segmentos de código para os aspectos de desempenho. Nos seguintes casos, é necessário dividir o código usando apenas o pré-processador.

  1. Código de várias plataformas:
    por exemplo, quando o código é compilado em plataformas diferentes, quando o código depende de números de versão específicos do SO (ou mesmo da versão do compilador - embora isso seja muito raro). Por exemplo, quando você está lidando com códigos de código de pequeno e grande endien - eles devem ser segregados com pré-processadores em vez de constantes. Ou, se você estiver compilando código para o Windows, Linux e certas chamadas do sistema são muito diferentes.

  2. Patches experimentais:
    Outro caso em que isso é justificado é algum código experimental que é arriscado ou alguns módulos importantes que precisam ser omitidos que terão uma ligação ou diferença de desempenho significativa. A razão pela qual alguém desejaria desativar o código via pré-processador, em vez de se esconder em if (), é porque podemos não ter certeza dos bugs introduzidos por esse conjunto de alterações específico e estamos executando com base experimental. Se falhar, não devemos fazer nada além de desabilitar esse código na produção do que reescrever. Algum tempo é ideal #if 0 para comentar o código inteiro.

  3. Lidando com dependências:
    Outro motivo em que você pode gerar Por exemplo, se você não deseja suportar imagens JPEG, pode ajudar a se livrar da compilação desse módulo / stub e, eventualmente, a biblioteca não vinculará (estaticamente ou dinamicamente) a isso módulo. Às vezes, os pacotes são executados ./configurepara identificar a disponibilidade dessa dependência e se as bibliotecas não estiverem presentes (ou o usuário não quiser habilitar), essa funcionalidade será desabilitada automaticamente sem vincular-se a essa biblioteca. Aqui é sempre benéfico se essas diretivas forem geradas automaticamente.

  4. Licenciamento:
    Um exemplo muito interessante de diretiva de pré-processador é o ffmpeg . Possui codecs que podem potencialmente infringir patentes por seu uso. Se você baixar o código-fonte e compilar para instalar, ele pergunta se você deseja ou mantém essas coisas afastadas. Manter códigos ocultos sob algumas condições, se as condições ainda puderem levar você a tribunal!

  5. Código copiar e colar:
    Macros Aka. Este não é um conselho para o uso excessivo de macros - apenas que as macros têm uma maneira muito mais poderosa de aplicar o equivalente ao passado da cópia . Mas use-o com muito cuidado; e use-o se você souber o que está fazendo. Constantes, é claro, não podem fazer isso. Mas pode-se usar inlinefunções também se isso for fácil de fazer.

Então, quando você usa constantes?
Quase em qualquer outro lugar.

  1. Fluxo de código mais organizado:
    em geral, quando você usa constantes, é quase indistinguível das variáveis ​​regulares e, portanto, é um código legível melhor. Se você escrever uma rotina de 75 linhas - tenha 3 ou 4 linhas após cada 10 linhas com #ifdef MUITO incapaz de ler . Provavelmente, dada uma constante primária governada pelo #ifdef e use-a em um fluxo natural em todos os lugares.

  2. Código bem recuado: Todas as diretivas de pré-processador, nunca funcionam bem com código recuado . Mesmo se o seu compilador permitir a indentação de #def, o pré-processador C do Pré-ANSI C não permitiu espaço entre o início de uma linha e o caractere "#"; o "#" principal sempre deveria ser colocado na primeira coluna.

  3. Configuração:
    Outra razão pela qual constantes / ou variáveis ​​fazem sentido é que elas podem evoluir facilmente de serem vinculadas a globais ou no futuro podem ser estendidas para serem derivadas de arquivos de configuração.

Uma última coisa:
nunca USE diretivas de pré-processador #ifdefpara #endif cruzar o escopo ou { ... }. ou seja, início #ifdefou fim de #endifem lados diferentes de { ... }. Isso é extremamente ruim; pode ser confuso, pode ser perigoso em algum momento.

Obviamente, isso não é uma lista exaustiva, mas mostra uma grande diferença, onde o método é mais adequado para uso. Não se trata realmente de qual é o melhor , é sempre mais do que um que é mais natural usar em determinado contexto.

Dipan Mehta
fonte
Obrigado pela resposta longa e detalhada. Eu estava ciente da maioria das coisas que você mencionou e elas foram omitidas da pergunta, pois a questão não é se os recursos do pré-processador devem ser usados, mas sobre um tipo específico de uso. Mas acho que o argumento básico que você fez é verdadeiro.
MByD 25/11/11
11
"Nunca USE diretivas de pré-processador #ifdef para #endif cruzando o escopo ou {...}" isso depende do IDE. Se o IDE reconhecer blocos inativos de pré-processador e os recolher ou mostrar com cores diferentes, não será confuso.
Abyx 01/12/11
@Abyx, é verdade que o IDE pode ajudar. No entanto, em muitos casos em que as pessoas desenvolvem sob a plataforma * nix (linux etc.) - a escolha de editores etc. são muitas (VI, emacs e assim por diante). Portanto, alguém pode estar usando um editor diferente do seu.
Dipan Mehta 02/12/19
4

Seus clientes têm acesso ao seu código? Se sim, o pré-processador pode ser uma opção melhor. Temos algo semelhante e usamos sinalizadores de tempo de compilação para vários clientes (ou recursos específicos do cliente). Em seguida, um script pode extrair o código específico do cliente e nós enviamos esse código. Os clientes não estão cientes de outros clientes ou de outros recursos específicos do cliente.

Aditya Sehgal
fonte
3

Alguns pontos adicionais dignos de menção:

  1. O compilador pode processar alguns tipos de expressões constantes que o pré-processador não pode. Por exemplo, o pré-processador geralmente não tem como avaliar sizeof()em tipos não primitivos. Isso pode forçar o uso if()em alguns casos.

  2. O compilador ignorará a maioria dos problemas de sintaxe no código que é ignorado #if(alguns problemas relacionados ao pré-processador ainda podem causar problemas), mas insiste na correção sintática do código ignorado if(). Portanto, se o código que é pulado para algumas compilações, mas não para outras, se torna inválido como conseqüência de alterações em outras partes do arquivo (por exemplo, identificadores renomeados), geralmente ocorrerá todas as compilações se estiver desabilitado, if()mas não desabilitado por ele #if. Dependendo do motivo pelo qual o código está sendo ignorado, isso pode ou não ser uma coisa boa.

  3. Alguns compiladores omitem código inacessível, enquanto outros não; declarações de duração de armazenamento estático em código inacessível, no entanto, incluindo literais de seqüência de caracteres, podem alocar espaço, mesmo que nenhum código possa usar o espaço alocado.

  4. As macros podem usar if()testes dentro delas, mas, infelizmente, não há mecanismo para o pré-processador executar qualquer lógica condicional em uma macro [observe que, para uso em macros, o ? :operador geralmente pode ser melhor que if, mas os mesmos princípios se aplicam].

  5. A #ifdiretiva pode ser usada para controlar quais macros são definidas.

Eu acho que if()geralmente é mais limpo para a maioria dos casos, exceto aqueles que envolvem a tomada de decisões com base em qual compilador está usando ou em quais macros devem ser definidas; alguns dos problemas acima podem exigir o uso #if, no entanto, mesmo nos casos em ifque parecerem mais limpos.

supercat
fonte
1

Para encurtar a história: os receios sobre as diretivas de pré-processador são basicamente 2, inclusões múltiplas e talvez código menos legível e uma lógica mais enigmática.

Se o seu projeto for pequeno, quase sem um grande plano para o futuro, acho que as duas opções oferecem a mesma proporção entre prós e contras, mas se o seu projeto for grande, considere algo como escrever um arquivo de cabeçalho separado, se você ainda deseja usar diretivas de pré-processador, mas lembre-se de que esse tipo de abordagem geralmente torna o código menos legível e certifique-se de que o nome dessas constantes signifique algo para a lógica comercial do próprio programa.

Micro
fonte
Essa é uma excelente base de código, e o define em alguns cabeçalhos separados.
MByD 24/11/11