¿Por qué solo definir una macro si aún no está definida?


93

En toda nuestra base de código C, veo cada macro definida de la siguiente manera:

#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

¿Cuál es la razón fundamental de realizar estas comprobaciones de definición en lugar de simplemente definir las 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

No puedo encontrar esta práctica explicada en ninguna parte de la web.


6
Se garantiza que el cambio de constantes en otra parte del código funcionará de esta manera. Si en algún otro lugar alguien define una de esas macros, el preprocesador no las sobrescribirá cuando analice este archivo.
Enzo Ferber

8
Es un ejemplo del principio de diseño WET.
stark

Publicó una respuesta con un ejemplo, intente compilarla.
Enzo Ferber

Respuestas:


141

Esto le permite anular las macros cuando está compilando:

gcc -DMACRONAME=value

Las definiciones del archivo de encabezado se utilizan de forma predeterminada.


51

Como dije en el comentario, imagina esta situación:

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;
}

Imprimirá 44.

Sin embargo, si el condicional ifndefno estuviera allí, el resultado sería advertencias de compilación de redefinición de MACRO y se imprimirá 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
 ^

1
Esto es específico del compilador. Redefinir una macro similar a un objeto es ilegal a menos que la redefinición sea "la misma" (hay una especificación más técnica para eso, pero no es importante aquí). El código ilegal requiere un diagnóstico y, habiendo emitido un diagnóstico (aquí una advertencia), el compilador es libre de hacer cualquier cosa, incluso compilar el código con resultados específicos de la implementación.
Pete Becker

7
Si tiene definiciones en conflicto para la misma macro, ¿no preferiría recibir la advertencia en la mayoría de los casos? En lugar de usar silenciosamente la primera definición (porque la segunda usa un ifdefpara evitar redefinir).
Peter Cordes

@PeterCordes La mayoría de las veces, las definiciones en #infdefs se utilizan como valores "alternativos" o "predeterminados". Básicamente, "si el usuario lo configuró, está bien. Si no, usemos un valor predeterminado".
Angew ya no está orgulloso de SO

@Angew: Ok, así que si usted tiene un poco #definesen una cabecera de biblioteca que forman parte de la biblioteca de la ABI, debería no envolverlos en #ifndef. (O mejor, use un enum). Solo quería dejar en claro que #ifndefsolo es apropiado cuando tener una definición personalizada para algo en una unidad de compilación, pero no en otra, está bien. Si a.cincluye encabezados en un orden diferente al de b.c, es posible que obtengan diferentes definiciones de max(a,b), y una de esas definiciones puede romper max(i++, x), pero la otra puede usar temporales en una expresión-declaración de GNU. ¡Todavía confuso al menos!
Peter Cordes

@PeterCordes Lo que me gusta hacer en ese caso es#ifdef FOO #error FOO already defined! #endif #define FOO x
Cole Johnson

17

No conozco el contexto, pero esto se puede usar para darle al usuario la disponibilidad para anular los valores establecidos por esas definiciones de macro. Si el usuario define explícitamente un valor diferente para cualquiera de esas macros, se utilizará en lugar de los valores utilizados aquí.

Por ejemplo, en g ++ puede usar la -Dbandera durante la compilación para pasar un valor a una macro.


14

Esto se hace para que el usuario del archivo de encabezado pueda anular las definiciones de su código o del indicador -D del compilador.


7

Cualquier proyecto de C reside en varios archivos fuente. Cuando se trabaja en un solo archivo de origen, las comprobaciones parecen (y de hecho) no tienen sentido, pero cuando se trabaja en un proyecto C grande, es una buena práctica comprobar las definiciones existentes antes de definir una constante. La idea es simple: necesita la constante en ese archivo fuente específico, pero puede que ya esté definida en otro.


2

Podría pensar en un marco / biblioteca que le brinde al usuario un ajuste preestablecido predeterminado que le permita compilar y trabajar en él. Esas definiciones se distribuyen en diferentes archivos y se recomienda al usuario final que incluya su archivo config.h donde puede configurar sus valores. Si el usuario olvidó alguna definición, el sistema puede continuar funcionando debido a la configuración predeterminada.


1

Utilizando

#ifndef BEEPTRIM_PITCH_RATE_DEGPS
#define BEEPTRIM_PITCH_RATE_DEGPS                   0.2f
#endif

permite al usuario definir el valor de la macro usando el argumento de la línea de comando (en gcc / clang / VS) -DBEEPTRIM_PITCH_RATE_DEGPS=0.3f.

Hay otra razón importante. Es un error volver a definir una macro de preprocesador de manera diferente. Vea esta respuesta a otra pregunta SO . Sin la #ifndefverificación, el compilador debería producir un error si -DBEEPTRIM_PITCH_RATE_DEGPS=0.3fse usa como argumento de línea de comando en la invocación del compilador.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.