Buscar..


Introducción

En la C moderna, los archivos de encabezado son herramientas cruciales que deben diseñarse y usarse correctamente. Permiten al compilador realizar una verificación cruzada de partes compiladas independientemente de un programa.

Los encabezados declaran los tipos, funciones, macros, etc. que necesitan los consumidores de un conjunto de instalaciones. Todo el código que utiliza cualquiera de esas instalaciones incluye el encabezado. Todo el código que define esas facilidades incluye el encabezado. Esto permite que el compilador compruebe que los usos y las definiciones coinciden.

Introducción

Hay una serie de pautas a seguir al crear y usar archivos de encabezado en un proyecto de C:

  • Idemopotencia

    Si un archivo de encabezado se incluye varias veces en una unidad de traducción (TU), no debe interrumpir las compilaciones.

  • Autocontención

    Si necesita las instalaciones declaradas en un archivo de encabezado, no debería tener que incluir ningún otro encabezado explícitamente.

  • Minimalidad

    No debería ser capaz de eliminar ninguna información de un encabezado sin causar errores en las compilaciones.

  • Incluye lo que usas (IWYU)

    De mayor preocupación para C ++ que para C, pero también importante en C. Si el código en una TU ( code.c ) usa directamente las características declaradas por un encabezado ( "headerA.h" ), entonces code.c debería #include "headerA.h" directamente, incluso si la TU incluye otro encabezado ( "headerB.h" ) que sucede, en este momento, para incluir "headerA.h" .

Ocasionalmente, puede haber razones suficientes para romper una o más de estas pautas, pero ambos deben ser conscientes de que están infringiendo la regla y ser conscientes de las consecuencias de hacerlo antes de romperla.

Idempotencia

Si un archivo de encabezado en particular se incluye más de una vez en una unidad de traducción (TU), no debería haber ningún problema de compilación. Esto se denomina 'idempotencia'; Sus encabezados deben ser idempotentes. Piense lo difícil que sería la vida si tuviera que asegurarse de que #include <stdio.h> solo se incluyera una vez.

Hay dos formas de lograr la idempotencia: los protectores de encabezado y la directiva #pragma once .

Guardias de cabecera

Los protectores de encabezado son simples y confiables y se ajustan al estándar C. Las primeras líneas sin comentarios en un archivo de encabezado deben tener el siguiente formato:

#ifndef UNIQUE_ID_FOR_HEADER
#define UNIQUE_ID_FOR_HEADER

La última línea sin comentarios debe ser #endif , opcionalmente con un comentario después de ella:

#endif /* UNIQUE_ID_FOR_HEADER */

Todos los códigos operativos, incluidas otras directivas #include , deben estar entre estas líneas.

Cada nombre debe ser único. A menudo, se utiliza un esquema de nombre como HEADER_H_INCLUDED . Algunos códigos más antiguos utilizan un símbolo definido como la protección del encabezado (por ejemplo, #ifndef BUFSIZ en <stdio.h> ), pero no es tan confiable como un nombre único.

Una opción sería usar un hash MD5 (u otro) generado para el nombre del guardián del encabezado. Debe evitar emular los esquemas utilizados por los encabezados del sistema, que con frecuencia utilizan nombres reservados para la implementación; los nombres comienzan con un guión bajo seguido de otro guión bajo o una letra mayúscula.

La #pragma once Directiva

Alternativamente, algunos compiladores admiten la directiva #pragma once que tiene el mismo efecto que las tres líneas mostradas para los guardias de encabezado.

#pragma once

Los compiladores que admiten #pragma once incluyen MS Visual Studio, GCC y Clang. Sin embargo, si la portabilidad es una preocupación, es mejor usar protectores de encabezado, o usar ambos. Los compiladores modernos (aquellos que soportan C89 o posterior) deben ignorar, sin comentarios, los pragmas que no reconocen ('Cualquier pragma que no sea reconocido por la implementación es ignorado') pero las versiones antiguas de GCC no eran tan indulgentes.

Autocontención

Los encabezados modernos deben ser autocontenidos, lo que significa que un programa que necesita usar las facilidades definidas por header.h puede incluir ese encabezado ( #include "header.h" ) y no preocuparse de si otros encabezados deben incluirse primero.

Recomendación: Los archivos de encabezado deben ser autocontenidos.


Reglas historicas

Históricamente, este ha sido un tema ligeramente contencioso.

Una vez en otro milenio, los estándares de codificación y estilo de AT&T Indian Hill C establecían:

Los archivos de encabezado no deben estar anidados. El prólogo de un archivo de encabezado debe, por lo tanto, describir qué otros encabezados deben ser #include d para que el encabezado sea funcional. En casos extremos, cuando se debe incluir una gran cantidad de archivos de encabezado en varios archivos fuente diferentes, es aceptable colocar todos los #include s comunes en un archivo de inclusión.

Esta es la antítesis de la autocontención.

Reglas modernas

Sin embargo, desde entonces, la opinión ha tendido en la dirección opuesta. Si un archivo de origen necesita usar las instalaciones declaradas por un encabezado header.h , el programador debería poder escribir:

#include "header.h"

y (sujeto solo a que se hayan establecido las rutas de búsqueda correctas en la línea de comando), header.h cualquier encabezado necesario necesario sin que sea necesario agregar más encabezados al archivo fuente.

Esto proporciona una mejor modularidad para el código fuente. También protege la fuente del enigma "adivina por qué se agregó este encabezado" que surge después de que el código haya sido modificado y pirateado durante una o dos décadas.

Los estándares de codificación de la NASA para el Centro de Vuelo Espacial Goddard (GSFC) para C es uno de los estándares más modernos, pero ahora es un poco difícil de rastrear. Establece que los encabezados deben ser autocontenidos. También proporciona una forma sencilla de garantizar que los encabezados sean independientes: el archivo de implementación del encabezado debe incluir el encabezado como primer encabezado. Si no es autocontenido, ese código no se compilará.

La razón dada por GSFC incluye:

§2.1.1 Encabezado incluye fundamento

Este estándar requiere que el encabezado de una unidad contenga #include instrucciones para todos los demás encabezados requeridos por el encabezado de la unidad. La colocación de #include para el encabezado de la unidad primero en el cuerpo de la unidad le permite al compilador verificar que el encabezado contiene todas las declaraciones de #include requeridas.

Un diseño alternativo, no permitido por esta norma, no permite #include declaraciones en los encabezados; Todos los #includes se realizan en los archivos del cuerpo. Los archivos de encabezado de unidad luego deben contener declaraciones #ifdef que verifiquen que los encabezados requeridos estén incluidos en el orden correcto.

Una de las ventajas del diseño alternativo es que la lista #include en el archivo del cuerpo es exactamente la lista de dependencias necesaria en un archivo make, y el compilador verifica esta lista. Con el diseño estándar, se debe usar una herramienta para generar la lista de dependencias. Sin embargo, todos los entornos de desarrollo recomendados por las sucursales proporcionan dicha herramienta.

Una desventaja importante del diseño alternativo es que si la lista de encabezados requerida de una unidad cambia, cada archivo que usa esa unidad debe editarse para actualizar la lista de la instrucción #include . Además, la lista de encabezados requerida para una unidad de biblioteca del compilador puede ser diferente en diferentes objetivos.

Otra desventaja del diseño alternativo es que los archivos de encabezado de la biblioteca del compilador, y otros archivos de terceros, deben modificarse para agregar las declaraciones necesarias de #ifdef .

Así, la autocontención significa que:

  • Si un encabezado header.h necesita un nuevo encabezado anidado extra.h , no tiene que verificar todos los archivos de origen que usan header.h para ver si necesita agregar extra.h .
  • Si un encabezado header.h ya no necesita incluir un encabezado específico notneeded.h , no tiene que verificar todos los archivos de origen que usan header.h para ver si puede eliminar notneeded.h forma notneeded.h (pero vea Incluir lo que usa) .
  • No es necesario establecer la secuencia correcta para incluir los encabezados de requisitos previos (lo que requiere una clasificación topológica para hacer el trabajo correctamente).

Comprobando la autocontención.

Consulte Vinculación contra una biblioteca estática para ver un script chkhdr que se puede usar para probar la identidad y la autocontención de un archivo de encabezado.

Minimalidad

Los encabezados son un mecanismo de verificación de consistencia crucial, pero deben ser lo más pequeños posible. En particular, eso significa que un encabezado no debe incluir otros encabezados solo porque el archivo de implementación necesitará los otros encabezados. Un encabezado debe contener solo los encabezados necesarios para un consumidor de los servicios descritos.

Por ejemplo, un encabezado de proyecto no debe incluir <stdio.h> menos que una de las interfaces de función use el tipo FILE * (o uno de los otros tipos definidos únicamente en <stdio.h> ). Si una interfaz usa size_t , el encabezado más pequeño que basta es <stddef.h> . Obviamente, si se incluye otro encabezado que define size_t , no es necesario incluir <stddef.h> también.

Si los encabezados son mínimos, también se reduce al mínimo el tiempo de compilación.

Es posible diseñar encabezados cuyo único propósito es incluir muchos otros encabezados. Esto rara vez resulta ser una buena idea a largo plazo porque pocos archivos de origen realmente necesitarán todas las facilidades descritas por todos los encabezados. Por ejemplo, se podría <standard-ch> un <standard-ch> que incluya todos los encabezados C estándar, con cuidado, ya que algunos encabezados no siempre están presentes. Sin embargo, muy pocos programas usan las instalaciones de <locale.h> o <tgmath.h> .

Incluye lo que usas (IWYU)

El proyecto Incluir lo que usa de Google, o IWYU, garantiza que los archivos de origen incluyan todos los encabezados utilizados en el código.

Supongamos que un archivo de origen source.c incluye un encabezado arbitrary.h que a su vez incluye freeloader.h , pero el archivo de origen también utiliza explícita e independientemente las instalaciones de freeloader.h . Todo está bien para empezar. Entonces, un día se cambia arbitrary.h para que sus clientes ya no necesiten las facilidades de freeloader.h . De repente, source.c deja de compilar, porque no cumple con los criterios de IWYU. Debido a que el código en source.c usaba explícitamente las facilidades de freeloader.h , debería haber incluido lo que usa; debería haber un explícito #include "freeloader.h" en la fuente también. (La idempotencia hubiera asegurado que no hubiera ningún problema.)

La filosofía IWYU maximiza la probabilidad de que el código continúe compilando incluso con cambios razonables realizados en las interfaces. Claramente, si su código llama a una función que se elimina posteriormente de la interfaz publicada, ninguna cantidad de preparación puede evitar que los cambios sean necesarios. Esta es la razón por la que se evitan los cambios en las API cuando es posible, y por qué hay ciclos de desaprobación en varias versiones, etc.

Este es un problema particular en C ++ porque los encabezados estándar pueden incluirse entre sí. El archivo de origen file.cpp podría incluir un encabezado header1.h que en una plataforma incluye otro encabezado header2.h . file.cpp puede resultar que use las facilidades de header2.h también. Esto no sería un problema inicialmente: el código se compilaría porque header1.h incluye header2.h . En otra plataforma, o una actualización de la plataforma actual, header1.h podría ser revisado para que ya no incluya header2.h , y entonces file.cpp dejaría de compilar como resultado.

IWYU detectaría el problema y recomendaría que header2.h se incluya directamente en file.cpp . Esto aseguraría que continúe compilando. También se aplican consideraciones análogas al código C

Notación y Miscelánea

El estándar C dice que hay muy poca diferencia entre las #include <header.h> y #include "header.h" .

[ #include <header.h> ] busca una secuencia de lugares definidos por la implementación para un encabezado identificado únicamente por la secuencia especificada entre los delimitadores < y > , y provoca el reemplazo de esa directiva por todo el contenido del encabezado. La forma en que se especifican los lugares o el encabezado identificado se define por la implementación.

[ #include "header.h" ] provoca el reemplazo de esa directiva por todo el contenido del archivo fuente identificado por la secuencia especificada entre los delimitadores "…" . El archivo fuente nombrado se busca de una manera definida por la implementación. Si esta búsqueda no es compatible, o si la búsqueda falla, la directiva se vuelve a #include <header.h> como si leyera [ #include <header.h> ] ...

Por lo tanto, la forma de doble cita puede verse en más lugares que la forma de ángulo entre corchetes. El estándar especifica, por ejemplo, que los encabezados estándar deberían incluirse entre paréntesis angulares, aunque la compilación funciona si utiliza comillas dobles en su lugar. De manera similar, estándares como POSIX usan el formato de ángulo entre corchetes, y usted también debería hacerlo. Reserve los encabezados de comillas dobles para los encabezados definidos por el proyecto. Para los encabezados definidos externamente (incluidos los encabezados de otros proyectos en los que se basa su proyecto), la notación de ángulo-corchete es la más apropiada.

Tenga en cuenta que debe haber un espacio entre #include y el encabezado, aunque los compiladores no acepten ningún espacio allí. Los espacios son baratos.

Varios proyectos usan una notación tal como:

#include <openssl/ssl.h>
#include <sys/stat.h>
#include <linux/kernel.h>

Debería considerar si usar ese control de espacio de nombres en su proyecto (probablemente sea una buena idea). Debe mantenerse alejado de los nombres utilizados por los proyectos existentes (en particular, tanto sys como linux serían malas elecciones).

Si usa esto, su código debe ser cuidadoso y consistente en el uso de la notación.

No utilice la notación #include "../include/header.h" .

Los archivos de encabezado rara vez deben definir variables. Aunque mantendrá las variables globales al mínimo, si necesita una variable global, la declarará en un encabezado y la definirá en un archivo fuente adecuado, y ese archivo de origen incluirá el encabezado para verificar la declaración y la definición. y todos los archivos de origen que usan la variable usarán el encabezado para declararla.

Corolario: no declarará variables globales en un archivo de origen; un archivo de origen solo contendrá definiciones.

Los archivos de encabezado rara vez deben declarar funciones static , con la notable excepción de las funciones en static inline que se definirán en los encabezados si la función es necesaria en más de un archivo fuente.

  • Los archivos fuente definen variables globales y funciones globales.
  • Los archivos de origen no declaran la existencia de variables o funciones globales; Incluyen el encabezado que declara la variable o función.
  • Los archivos de encabezado declaran variables y funciones globales (y tipos y otro material de apoyo).
  • Los archivos de encabezado no definen variables o funciones, excepto las funciones en inline ( static ).

Referencias cruzadas



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow