Por que apenas definir uma macro se ainda não estiver definida?

93

Em toda a nossa base de código C, vejo cada macro definida da seguinte maneira:

#ifndef BEEPTRIM_PITCH_RATE_DEGPS
#define BEEPTRIM_PITCH_RATE_DEGPS                   0.2f
#endif

#ifndef BEEPTRIM_ROLL_RATE_DEGPS
#define BEEPTRIM_ROLL_RATE_DEGPS                    0.2f
#endif

#ifndef FORCETRIMRELEASE_HOLD_TIME_MS
#define FORCETRIMRELEASE_HOLD_TIME_MS               1000.0f
#endif

#ifndef TRIMSYSTEM_SHEARPIN_BREAKINGFORCE_LBS
#define TRIMSYSTEM_SHEARPIN_BREAKINGFORCE_LBS       50.0f
#endif

Qual é a lógica de fazer essas verificações de definição em vez de apenas definir as macros?

#define BEEPTRIM_PITCH_RATE_DEGPS                   0.2f
#define BEEPTRIM_ROLL_RATE_DEGPS                    0.2f
#define FORCETRIMRELEASE_HOLD_TIME_MS               1000.0f
#define TRIMSYSTEM_SHEARPIN_BREAKINGFORCE_LBS       50.0f

Não consigo encontrar essa prática explicada em nenhum lugar da web.

Trevor Hickey
fonte
6
Alterar constantes em algum outro lugar do código funcionará dessa maneira. Se em algum outro lugar alguém definir uma dessas macros, elas não serão substituídas pelo pré-processador quando ele analisar este arquivo.
Enzo Ferber,
8
É um exemplo do princípio de design WET.
Stark
Postou uma resposta com um exemplo, tente compilá-lo.
Enzo Ferber

Respostas:

141

Isso permite que você substitua as macros durante a compilação:

gcc -DMACRONAME=value

As definições no arquivo de cabeçalho são usadas como padrões.

Barmar
fonte
51

Como disse no comentário, imagine esta situação:

foo.h

#define FOO  4

defs.h

#ifndef FOO
#define FOO 6
#endif

#ifndef BAR
#define BAR 4
#endif

bar.c

#include "foo.h"
#include "defs.h"

#include <stdio.h>

int main(void)
{
    printf("%d%d", FOO, BAR);
    return 0;
}

Irá imprimir 44.

No entanto, se a condicional ifndefnão estivesse lá, o resultado seriam avisos de compilação de redefinição de MACRO e ela será impressa 64.

$ gcc -o bar bar.c
In file included from bar.c:2:0:
defs.h:1:0: warning: "FOO" redefined [enabled by default]
 #define FOO 6
 ^
In file included from bar.c:1:0:
foo.h:1:0: note: this is the location of the previous definition
 #define FOO 4
 ^
Enzo Ferber
fonte
1
Isso é específico do compilador. Redefinir uma macro semelhante a um objeto é ilegal, a menos que a redefinição seja "a mesma" (há uma especificação mais técnica para isso, mas não é importante aqui). O código ilegal requer um diagnóstico e, tendo emitido um diagnóstico (aqui um aviso), o compilador está livre para fazer qualquer coisa, incluindo compilar o código com resultados específicos da implementação.
Pete Becker,
7
Se você tiver defs conflitantes para a mesma macro, não prefere receber o aviso na maioria dos casos? Em vez de usar silenciosamente a primeira definição (porque a segunda usa um ifdefpara evitar a redefinição).
Peter Cordes
@PeterCordes Na maioria das vezes, as definições em #infdefs são usadas como valores "substitutos" ou "padrão". Basicamente, "se o usuário configurou, tudo bem. Se não, vamos usar um valor padrão."
Angew não está mais orgulhoso de SO
@Angew: Ok, então se você tiver alguns #definesem um cabeçalho de biblioteca que fazem parte da ABI da biblioteca, você não deve envolvê-los #ifndef. (Ou melhor, use um enum). Eu só queria deixar claro que #ifndefsó é apropriado quando se tem uma definição personalizada para algo em uma unidade de compilação, mas não em outra, está ok. Se a.cinclui cabeçalhos em uma ordem diferente de b.c, eles podem obter diferentes definições de max(a,b), e uma dessas definições pode quebrar max(i++, x), mas a outra pode usar temporários em uma expressão de instrução GNU. Ainda confuso, pelo menos!
Peter Cordes
@PeterCordes O que eu gosto de fazer nesse caso é#ifdef FOO #error FOO already defined! #endif #define FOO x
Cole Johnson
17

Eu não sei o contexto, mas isso pode ser usado para dar ao usuário a disponibilidade de substituir os valores definidos por essas definições de macro. Se o usuário definir explicitamente um valor diferente para qualquer uma dessas macros, ele será usado no lugar dos valores usados ​​aqui.

Por exemplo, no g ++ você pode usar o -Dsinalizador durante a compilação para passar um valor para uma macro.

Ivaylo Strandjev
fonte
14

Isso é feito para que o usuário do arquivo de cabeçalho possa substituir as definições de seu código ou do sinalizador -D do compilador.


fonte
7

Qualquer projeto C reside em vários arquivos de origem. Ao trabalhar em um único arquivo de origem, as verificações parecem (e na verdade) não têm sentido, mas ao trabalhar em um grande projeto C, é uma boa prática verificar as definições existentes antes de definir uma constante. A ideia é simples: você precisa da constante naquele arquivo fonte específico, mas pode já ter sido definida em outro.

George
fonte
2

Você poderia pensar em uma estrutura / biblioteca que dá ao usuário uma predefinição padrão que permite ao usuário compilar e trabalhar nela. Essas definições estão espalhadas em arquivos diferentes e o usuário final é aconselhado a incluir seu arquivo config.h onde ele pode configurar seus valores. Se o usuário esqueceu alguma definição, o sistema pode continuar a funcionar por causa da predefinição.

LPs
fonte
1

Usando

#ifndef BEEPTRIM_PITCH_RATE_DEGPS
#define BEEPTRIM_PITCH_RATE_DEGPS                   0.2f
#endif

permite ao usuário definir o valor da macro usando o argumento da linha de comando (em gcc / clang / VS) -DBEEPTRIM_PITCH_RATE_DEGPS=0.3f.

Existe outro motivo importante. É um erro redefinir uma macro do pré-processador de maneira diferente. Veja esta resposta a outra pergunta do SO . Sem a #ifndefverificação, o compilador deve produzir um erro se -DBEEPTRIM_PITCH_RATE_DEGPS=0.3ffor usado como um argumento de linha de comando na chamada do compilador.

R Sahu
fonte