¿Qué hacen exactamente los guardias que incluyen C?
macros c-preprocessor (3)
Tengo una pregunta sobre incluir guardias en C. He leído un poco, pero agradecería un poco de aclaración.
Digamos que tengo un archivo de encabezado "header.h" con una definición de función.
#ifndef HEADER_FILE
#define HEADER_FILE
int two(void){
return 2;
}
#endif
Este archivo de encabezado tiene un protector de inclusión. Sin embargo, estoy un poco confundido sobre lo que #define HEADER_FILE está haciendo realmente. Digamos que debía olvidar el protector de inclusión, hubiera sido perfectamente legal para mí ignorar completamente agregar ''#define HEADER_FILE''.
Entonces mi pregunta: ¿Qué estamos haciendo exactamente cuando definimos HEADER_FILE? ¿Qué estamos definiendo? ¿Y por qué está bien olvidar el protector de inclusión, en cuyo caso también podemos olvidar agregar #define HEADER_FILE?
Cualquier ayuda es apreciada!
En primer lugar, en la compilación moderna de C ++ puede usar
#pragma once
lugar de incluir guardias.
Entonces, su ejemplo es un poco confuso, porque define una función
extern
en su encabezado.
Normalmente, los archivos de
include
se utilizan para definir las declaraciones de funciones y no las definiciones de funciones.
Si define funciones en su encabezado y si este encabezado es utilizado por más de un archivo fuente de CPP, esta función se definirá más veces con el mismo nombre y tendrá un error cuando se vinculará el programa.
Una mejor inclusión sería
#ifndef HEADER_FILE
#define HEADER_FILE
int two(void);
#endif
o
#ifndef HEADER_FILE
#define HEADER_FILE
static int two(void) { return 2; }
#endif
o
#pragma once
static int two(void) { return 2; }
En el último caso, la función
two()
se define en cada archivo fuente de CPP que incluye este encabezado;
pero esta función es estática, por lo que las fuentes CPP se compilan correctamente y el programa CPP está vinculado sin problemas.
En tu pregunta, preguntas
en cuyo caso también podemos olvidar agregar #define HEADER_FILE?
Personalmente, uso el mismo encabezado en una situación difícil muy especial.
Los siguientes 2 incluyen son un "buen" ejemplo:
/*******************************************************************
* XTrace.Configuration.h
********************************************************************
*/
#pragma once
#define MODULEx(n) extern StructDefineMODULE MODULE_##n;
#include "XTrace.Modules.h"
#undef MODULEx
#define MODULEx(n) { #n, &MODULE_##n } ,
static struct ModuleTRACE tModuleTrace[]
= {
#include "XTrace.Modules.h"
{ 0, 0 }
};
donde
XTrace.Modules.h
include está siguiendo
/*******************************************************************
* XTrace.Modules.h
********************************************************************
*/
MODULEx( BBDIXFILE )
MODULEx( CECHO )
MODULEx( INITDBFIELD )
MODULEx( IVIRLUX )
La primera inclusión contiene
#pragma once
y llama a la misma inclusión interna 2 veces.
La primera vez que se llama para definir la declaración externa de la estructura StructDefineMODULE.
La segunda vez se llama para inicializar una matriz de estructuras ModuleTRACE.
Como esta inclusión se llama 2 veces, se debe evitar
#pragma once
o
#ifndef
.
Al usar una inclusión interna, estoy seguro al 100% de que todos los elementos utilizados para definir StructDefineModule también se utilizan para inicializar la matriz tModuleTrace [].
El resultado interno de incluir, sería
/*******************************************************************
* XTrace.Configuration.h
********************************************************************
*/
#pragma once
extern StructDefineMODULE MODULE_BBDIXFILE;
extern StructDefineMODULE MODULE_CECHO;
extern StructDefineMODULE MODULE_INITDBFIELD;
extern StructDefineMODULE MODULE_IVIRLUX;
static struct ModuleTRACE tModuleTrace[]
= { { "BBDIXFILE" , &MODULE_BBDIXFILE }
, { "CECHO" , &MODULE_CECHO }
, { "INITDBFIELD" , &MODULE_INITDBFIELD }
, { "IVIRLUX" , &MODULE_IVIRLUX }
, { 0, 0 }
};
Espero que esto pueda ayudarlo a comprender por qué, en algunas situaciones, incluir guardias puede evitarse.
Es una macro de preprocesador.
Todo es sintaxis de preprocesador, que básicamente dice, si esta macro aún no se ha definido,
#ifndef
e incluya todo el código entre
#ifndef
y
#endif
Lo que logra es evitar la inclusión de archivos más de una vez, lo que puede ocasionar problemas en su código.
Tu pregunta:
¿Y por qué está bien olvidar el protector de inclusión, en cuyo caso también podemos olvidar agregar #define HEADER_FILE?
Está bien olvidarlo porque todavía es código C legal sin él. El preprocesador procesa su archivo antes de que se compile e incluye el código especificado en su programa final si no hay una lógica que especifique por qué no debería. Es simplemente una práctica común, pero no es obligatoria.
Un ejemplo simple podría ayudar a ilustrar cómo funciona esto:
Su archivo de encabezado,
header_file.h
diremos, contiene esto:
#ifndef HEADER_FILE
#define HEADER_FILE
int two(void){
return 2;
}
#endif
En otro archivo (
foo.c
), es posible que tenga:
#include "header_file.h"
void foo() {
int value = two();
printf("foo value=%d/n", value);
}
A lo que esto se traducirá una vez que esté "preprocesado" y listo para la compilación es esto:
int two(void){
return 2;
}
void foo() {
int value = two();
printf("foo value=%d/n", value);
}
Todo lo que el protector de inclusión está logrando aquí es determinar si el contenido del encabezado entre
#ifndef ...
y
#endif
debe pegarse en lugar del
#include
original.
Sin embargo, dado que esa función no se declara
extern
o
static
, y en realidad se implementa en un archivo de encabezado, tendría un problema si intentara usarla en otro archivo fuente, ya que la definición de la función no se incluiría.
Evita que el archivo se incluya más de una vez, aquí
#ifndef HEADER_FILE
prueba si
HEADER_FILE
NO está definido, en caso de que sea cierto, entonces
#define HEADER_FILE
lo definiría, ahora si incluye el archivo en otro archivo, la primera vez definirá
HEADER_FILE
, mientras que la segunda vez, ya estará definido y, por lo tanto, el contenido del archivo no se volverá a incluir, ya que el
#ifndef HEADER_FILE
lo
#ifndef HEADER_FILE
ser falso
Recuerde que estos son evaluados por el preprocesador antes de que se realice la compilación real, por lo que se evalúan en el momento de la compilación.