Archivos de encabezado C/C++ y métodos para evitar la redefinición causada por la inclusión del archivo de encabezado

Archivos de encabezado C

Un archivo de encabezado es un archivo con la extensión  .h  que contiene declaraciones de funciones C y definiciones de macros, y varios archivos de origen hacen referencia a ellos y lo comparten. Hay dos tipos de archivos de encabezado: archivos de encabezado escritos por el programador y archivos de encabezado que vienen con el compilador.

Para usar un archivo de encabezado en un programa, debe usar la directiva de preprocesamiento de C  #include  para hacer referencia a él. Hemos visto  el archivo  de encabezado stdio.h anteriormente, que es el archivo de encabezado que viene con el compilador.

Hacer referencia a un archivo de encabezado es equivalente a copiar el contenido del archivo de encabezado, pero no copiamos el contenido del archivo de encabezado directamente en el archivo fuente, porque hacerlo es propenso a errores, especialmente cuando el programa se compone de varias fuentes. archivos

Una práctica simple en programas C o C++, se recomienda escribir todas las constantes, macros, variables globales del sistema y prototipos de funciones en archivos de encabezado, y consultar estos archivos de encabezado cuando sea necesario.

Sintaxis para hacer referencia a archivos de encabezado

 Se puede hacer referencia a los archivos de encabezado del usuario y del sistema mediante la directiva de preprocesamiento  #include . Tiene las siguientes dos formas:

#incluir <archivo>

Este formulario se utiliza para hacer referencia a los archivos de encabezado del sistema. Busca en la lista estándar de directorios del sistema un archivo llamado archivo. Al compilar el código fuente, puede anteponer directorios a esta lista con la opción -I.

#incluye "archivo"

Este formulario se utiliza para hacer referencia a los archivos de encabezado de usuario. Busca en el directorio que contiene el archivo actual un archivo llamado archivo. Al compilar el código fuente, puede anteponer directorios a esta lista con la opción -I.

Acciones para hacer referencia a archivos de encabezado

La directiva #include  le indica al preprocesador de C que explore el archivo especificado como entrada. La salida del preprocesador consta de la salida que se ha generado, la salida generada por los archivos a los que se hace referencia y  la salida de texto después de la  directiva #include . Por ejemplo, si tiene un archivo de encabezado header.h como este:

char *prueba (vacío);

y un programa principal  program.c que usa archivos de encabezado, de la siguiente manera:

int x;
#include "header.h"

int main (void)
{
   puts (test ());
}

El compilador verá la siguiente información de código:

int x;
char *test (void);

int main (void)
{
   puts (test ());
}

Solo haga referencia al archivo de encabezado una vez

Si se hace referencia dos veces a un archivo de encabezado, el compilador procesará el contenido del archivo de encabezado dos veces, lo que generará un error. Para evitar esto, la práctica estándar es colocar todo el contenido del archivo en una declaración de compilación condicional, como esta:

#ifndef HEADER_FILE
#define HEADER_FILE

the entire header file file

#endif

Esta construcción es lo que comúnmente se conoce como contenedor  #ifndef . Cuando se vuelve a hacer referencia al archivo de encabezado, la condición es falsa porque se define HEADER_FILE. En este punto, el preprocesador omite todo el contenido del archivo y el compilador lo ignora.

referencia condicional

A veces es necesario seleccionar uno de varios archivos de encabezado diferentes para hacer referencia a un programa. Por ejemplo, debe especificar los parámetros de configuración que se utilizan en diferentes sistemas operativos. Puedes hacer esto con una serie de condiciones, como sigue:

#if SYSTEM_1
   # include "system_1.h"
#elif SYSTEM_2
   # include "system_2.h"
#elif SYSTEM_3
   ...
#endif

Pero si hay muchos archivos de encabezado, no es apropiado hacerlo, el preprocesador usa macros para definir el nombre del archivo de encabezado. Esto se llama una referencia condicional . En lugar de tomar el nombre del archivo de encabezado como  un argumento directo para #incluir  , solo necesita usar el nombre de la macro en su lugar:

1 #define SYSTEM_H "system_1.h" 
2 ... 
3 #incluye SYSTEM_H

SYSTEM_H se expande y el preprocesador busca system_1.h como   se escribió originalmente #include . SYSTEM_H puede ser definido por su Makefile con la opción -D.

En un período de programación, a menudo se encuentran problemas de redefinición. Generalmente, la relación entre #include y la inclusión repetida ocurre cuando se incluye el archivo header.h. Si tiene suerte, puede encontrar fácilmente el problema.Si no tiene suerte, debe enumerar las relaciones de inclusión en todos los archivos de encabezado.H, y verificar una por una qué salió mal. Recientemente descubrí que si sigue el principio de " no incluir más el archivo de encabezado .h en el archivo de encabezado .h ", puede evitar fundamentalmente este problema. Aunque esto hará que el archivo de encabezado requerido .h deba incluirse en el archivo de código .c o .cpp, y también tenga en cuenta que puede haber problemas de orden al incluir, pero esto es más que buscar dónde redefinir Es mucho más simple y hace más clara la relación de contención.

Todos los archivos en el proyecto original han sido modificados de acuerdo con los principios anteriores, y hasta ahora no se han encontrado efectos adversos, lo cual no está mal.

Además, también puede agregar #pragma una vez al área precompilada para evitar la redefinición,

Tenga en cuenta el uso de #ifndef... #define... #end if y extern

¿Cuál es la diferencia entre el uso de #ifndef y #pragma una vez?

El código de ejemplo es el siguiente:

 método uno:

 #ifndef __ALGÚNARCHIVO_H__

#define __ALGUNOARCHIVO_H__

 ... ... // declaración, declaración de definición

#terminara si

   

Método dos:

#pragma una vez

 ... ... // declaración, declaración de definición

¿Cuáles son las características de los dos?

(1) #ifndef

  La forma de #ifndef es compatible con el estándar de lenguaje C/C++. No solo garantiza que el mismo archivo no se incluya varias veces, sino que también garantiza que dos archivos (o fragmentos de código) con exactamente el mismo contenido no se incluyan accidentalmente al mismo tiempo.

  Por supuesto, la desventaja es que si los nombres de las macros en diferentes archivos de encabezado "colisionan" accidentalmente, puede hacer que vea que el archivo de encabezado claramente existe, pero el compilador insiste en que no se puede encontrar la declaración; esta situación a veces es muy molesta. Deprimido.

  Dado que el compilador necesita abrir el archivo de encabezado cada vez para determinar si hay definiciones duplicadas, ifndef hará que el tiempo de compilación sea relativamente largo al compilar proyectos grandes, por lo que algunos compiladores gradualmente comienzan a admitir el método #pragma once.

(2) #pragma una vez

  #pragma una vez generalmente está garantizado por el compilador: el mismo archivo no se incluirá más de una vez. Tenga en cuenta que el "mismo archivo" mencionado aquí se refiere a un archivo físico, no a dos archivos con el mismo contenido.

  No puede hacer una declaración pragma una vez para una pieza de código en un archivo de encabezado, solo para archivos.

  La ventaja es que ya no tiene que preocuparse por los conflictos de nombres de macros y, por supuesto, no habrá problemas extraños causados ​​por conflictos de nombres de macros. La velocidad de compilación de proyectos grandes también se ha mejorado un poco.

  La desventaja correspondiente es que si hay varias copias de un archivo de encabezado, este método no puede garantizar que no se incluirán repetidamente. Por supuesto, en comparación con el problema de "declaración no encontrada" causado por nombres de macro en conflicto, tales inclusiones duplicadas son más fáciles de detectar y corregir.

  Además, ¡de esta manera no es compatible con varias plataformas!

¿Cuál es la conexión entre los dos?

 El enfoque de #pragma once vino después de #ifndef, por lo que muchas personas probablemente ni siquiera hayan oído hablar de él. Parece que #ifndef es más respetado en este momento. Porque #ifndef es compatible con el lenguaje estándar C/C++ y no está sujeto a ninguna restricción por parte del compilador;

 El método #pragma once no es compatible con algunos compiladores más antiguos, y algunos compiladores que lo admiten planean eliminarlo, por lo que su compatibilidad puede no ser lo suficientemente buena.

 En términos generales, cuando los programadores escuchan tales palabras, eligen el método #ifndef. Para tratar de hacer que su código "viva" más tiempo, generalmente prefieren reducir el rendimiento de la compilación. Esta es la personalidad del programador. Por supuesto, esto está fuera de tema.

 También vi un uso que pone los dos juntos:

     #pragma una vez

   #ifndef __ALGÚNARCHIVO_H__

   #define __ALGUNOARCHIVO_H__

   ... ... // declaración, declaración de definición

   #terminara si

Resumir:

        Parece que quiere tener lo mejor de ambos mundos. Sin embargo, mientras se use #ifndef, existe el peligro de un conflicto de nombres de macros, y es inevitable que los compiladores que no admiten #pragma una vez informen errores, por lo que mezclar los dos métodos no parece traer más beneficios, pero hará que algunas personas desconocidas se confundan.

        El método a elegir debe determinarse de acuerdo con la situación específica después de comprender los dos métodos. En mi opinión, cualquier forma es aceptable, siempre que exista una convención razonable para evitar los inconvenientes. Y esto ya no es responsabilidad del estándar o del compilador, debe ser hecho por los propios programadores o por una especificación de desarrollo a pequeña escala.

Para evitar que el mismo archivo se incluya varias veces

1 método #ifndef
2 método #pragma once

No hay mucha diferencia entre los dos en términos de compiladores que los admitan, pero aún existen algunas diferencias sutiles entre los dos.
    método uno:

    #ifndef __SOMEFILE_H__
    #define __SOMEFILE_H__
    ... ... // algunas sentencias de declaración
    #endif

    Método dos:

    #pragma una vez
    ... ... // algunas declaraciones


    El método #ifndef se basa en que el nombre de la macro no colisione, lo que no solo garantiza que el mismo archivo no se incluirá varias veces, sino que también garantiza que dos archivos con el mismo contenido no se incluirán accidentalmente al mismo tiempo. Por supuesto, la desventaja es que si los nombres de macro de diferentes archivos de encabezado "colisionan" accidentalmente, puede llevar al hecho de que el archivo de encabezado claramente existe, pero el compilador insiste en que no se puede encontrar la declaración.

    #pragma una vez está garantizado por el compilador: el mismo archivo no se incluirá varias veces. Tenga en cuenta que el "mismo archivo" mencionado aquí se refiere a un archivo físico, no a dos archivos con el mismo contenido. La ventaja es que no tiene que pensar demasiado en el nombre de una macro y, por supuesto, no habrá problemas extraños causados ​​por colisiones de nombres de macros. La desventaja correspondiente es que si hay varias copias de un archivo de encabezado, este método no puede garantizar que no se incluirán repetidamente. Por supuesto, en comparación con el problema de "no se puede encontrar la declaración" causado por la colisión de nombres de macros, las inclusiones duplicadas son más fáciles de encontrar y corregir.

   El primer método es compatible con el idioma, por lo que la portabilidad es buena y el segundo método puede evitar conflictos de nombres.

Supongo que te gusta

Origin blog.csdn.net/weixin_55305220/article/details/121998373
Recomendado
Clasificación