Recherche…


Introduction

Dans le moderne C, les fichiers d'en-tête sont des outils cruciaux qui doivent être conçus et utilisés correctement. Ils permettent au compilateur de vérifier par recoupement des parties d'un programme compilées de manière indépendante.

Les en-têtes déclarent les types, les fonctions, les macros, etc. nécessaires aux utilisateurs d'un ensemble de fonctions. Tout le code qui utilise l'une de ces fonctionnalités inclut l'en-tête. Tout le code qui définit ces fonctionnalités inclut l'en-tête. Cela permet au compilateur de vérifier que les utilisations et les définitions correspondent.

introduction

Il existe un certain nombre de directives à suivre lors de la création et de l’utilisation de fichiers d’en-tête dans un projet C:

  • Idemopotence

    Si un fichier d'en-tête est inclus plusieurs fois dans une unité de traduction (TU), il ne doit pas interrompre les générations.

  • Auto-confinement

    Si vous avez besoin des fonctionnalités déclarées dans un fichier d'en-tête, vous ne devriez pas avoir à inclure explicitement d'autres en-têtes.

  • Minimalité

    Vous ne devriez pas être en mesure de supprimer des informations d'un en-tête sans provoquer l'échec des builds.

  • Inclure ce que vous utilisez (IWYU)

    Plus préoccupant en C ++ que C, mais néanmoins important en C aussi. Si le code dans une TU (appelez-le code.c ) utilise directement les fonctionnalités déclarées par un en-tête (appelez-le "headerA.h" ), alors code.c devrait #include "headerA.h" directement, même si la TU inclut un autre en-tête (appelez-le "headerB.h" ) qui, pour le moment, inclut "headerA.h" .

Parfois, il peut y avoir des raisons suffisantes pour enfreindre une ou plusieurs de ces directives, mais vous devez être conscient que vous enfreignez la règle et être conscient des conséquences de le faire avant de le casser.

Idempotence

Si un fichier d'en-tête particulier est inclus plusieurs fois dans une unité de traduction (TU), il ne devrait pas y avoir de problèmes de compilation. C'est ce qu'on appelle «l'idempotence»; vos en-têtes doivent être idempotents. Pensez à quel point la vie serait difficile si vous deviez vous assurer que #include <stdio.h> n'était inclus qu'une seule fois.

Il y a deux façons de parvenir à idempotence: les gardes-têtes et la directive #pragma once .

Gardes de tête

Les protège-têtes sont simples et fiables et conformes à la norme C. Les premières lignes sans commentaire dans un fichier d'en-tête doivent être de la forme:

#ifndef UNIQUE_ID_FOR_HEADER
#define UNIQUE_ID_FOR_HEADER

La dernière ligne sans commentaire doit être #endif , éventuellement avec un commentaire après:

#endif /* UNIQUE_ID_FOR_HEADER */

Tout le code opérationnel, y compris les autres directives #include , doit se trouver entre ces lignes.

Chaque nom doit être unique. Souvent, un schéma de nom tel que HEADER_H_INCLUDED est utilisé. Un code plus ancien utilise un symbole défini comme garde d'en-tête (par exemple #ifndef BUFSIZ dans <stdio.h> ), mais il n'est pas aussi fiable qu'un nom unique.

Une option serait d'utiliser un hachage MD5 (ou autre) généré pour le nom de la garde d'en-tête. Vous devez éviter d'émuler les schémas utilisés par les en-têtes système qui utilisent fréquemment des noms réservés à l'implémentation - noms commençant par un trait de soulignement suivi d'un autre trait de soulignement ou d'une lettre majuscule.

La directive #pragma once

Certains compilateurs prennent également en charge la directive #pragma once qui a le même effet que les trois lignes affichées pour les gardes d’en-tête.

#pragma once

Les compilateurs qui supportent #pragma once incluent MS Visual Studio et GCC et Clang. Toutefois, si la portabilité est un problème, il est préférable d’utiliser des gardes d’en-tête ou d’utiliser les deux. Les compilateurs modernes (ceux prenant en charge C89 ou version ultérieure) doivent ignorer, sans commentaire, les pragmas qu'ils ne reconnaissent pas («Tout pragma qui n'est pas reconnu par l'implémentation est ignoré») mais les anciennes versions de GCC n'étaient pas si indulgentes.

Auto-confinement

Les en-têtes modernes doivent être autonomes, ce qui signifie qu'un programme devant utiliser les fonctionnalités définies par header.h peut inclure cet en-tête ( #include "header.h" ) et ne pas se soucier de savoir si d'autres en-têtes doivent être inclus en premier.

Recommandation: les fichiers d'en-tête doivent être autonomes.


Règles historiques

Historiquement, ce sujet a été légèrement controversé.

Un autre millénaire, les normes de codage et de codage AT & T d'Indian Hill ont déclaré:

Les fichiers d'en-tête ne doivent pas être imbriqués. Le prologue d'un fichier d'en-tête doit donc décrire quels autres en-têtes doivent être #include d pour que l'en-tête soit fonctionnel. Dans les cas extrêmes, où un grand nombre de fichiers d'en-tête doivent être inclus dans plusieurs fichiers source différents, il est acceptable de mettre tous les fichiers #include communs dans un fichier d'inclusion.

C'est l'antithèse de la maîtrise de soi.

Règles modernes

Cependant, depuis lors, les opinions ont évolué dans la direction opposée. Si un fichier source doit utiliser les fonctionnalités déclarées par un en-tête header.h , le programmeur doit pouvoir écrire:

#include "header.h"

et (uniquement sous réserve que les chemins de recherche corrects soient définis sur la ligne de commande), les en-têtes header.h nécessaires seront inclus par header.h sans avoir besoin d'ajouter d'autres en-têtes au fichier source.

Cela fournit une meilleure modularité pour le code source. Il protège également la source de l'énigme "pourquoi cet en-tête a été ajouté" qui survient après que le code a été modifié et piraté pendant une décennie ou deux.

Les normes de codage de la NASA Goddard Space Flight Center (GSFC) pour C sont l'une des normes les plus modernes - mais sont maintenant un peu difficiles à retrouver. Il indique que les en-têtes doivent être autonomes. Il fournit également un moyen simple de s’assurer que les en-têtes sont autonomes: le fichier d’implémentation de l’en-tête doit inclure l’en-tête comme premier en-tête. S'il n'est pas autonome, ce code ne sera pas compilé.

La justification donnée par GSFC comprend:

§2.1.1 En-tête inclure la justification

Cette norme exige que l'en-tête d'une unité contienne des instructions #include pour tous les autres en-têtes requis par l'en-tête de l'unité. Placer #include pour l'en-tête d'unité en premier dans le corps d'unité permet au compilateur de vérifier que l'en-tête contient toutes les instructions #include requises.

Une autre conception, non autorisée par cette norme, n'autorise aucune instruction #include dans les en-têtes; tous les #includes sont effectués dans les fichiers du corps. Les fichiers d'en-tête d'unité doivent alors contenir des instructions #ifdef qui vérifient que les en-têtes requis sont inclus dans le bon ordre.

L'un des avantages de la conception alternative est que la liste #include dans le fichier de corps est exactement la liste de dépendances requise dans un fichier make, et cette liste est vérifiée par le compilateur. Avec la conception standard, un outil doit être utilisé pour générer la liste des dépendances. Cependant, tous les environnements de développement recommandés par la branche fournissent un tel outil.

Un inconvénient majeur de la conception alternative est que si la liste d'en-tête requise d'une unité change, chaque fichier qui utilise cette unité doit être modifié pour mettre à jour la liste d'instructions #include . En outre, la liste d'en-tête requise pour une unité de bibliothèque de compilateur peut être différente sur différentes cibles.

Un autre inconvénient de la conception alternative est que les fichiers d'en-tête de la bibliothèque du compilateur et les autres fichiers tiers doivent être modifiés pour ajouter les instructions #ifdef requises.

Ainsi, l'auto-confinement signifie que:

  • Si un en-tête header.h besoin d'un nouvel en-tête imbriqué extra.h , vous n'avez pas à vérifier chaque fichier source qui utilise header.h pour voir si vous devez ajouter des extra.h .
  • Si un en-tête header.h n'a plus besoin d'inclure un en-tête spécifique notneeded.h , vous n'avez pas besoin de vérifier chaque fichier source qui utilise header.h pour voir si vous pouvez supprimer notneeded.h (mais voir Inclure ce que vous utilisez) .
  • Vous n'avez pas à établir la séquence correcte pour inclure les en-têtes pré-requis (ce qui nécessite un tri topologique pour effectuer le travail correctement).

Vérification de l'auto-confinement

Voir Liaison avec une bibliothèque statique pour un script chkhdr qui peut être utilisé pour tester l'idempotence et l'auto-confinement d'un fichier d'en-tête.

Minimalité

Les en-têtes sont un mécanisme de vérification de la cohérence crucial, mais ils doivent être aussi petits que possible. En particulier, cela signifie qu'un en-tête ne doit pas inclure d'autres en-têtes simplement parce que le fichier d'implémentation aura besoin des autres en-têtes. Un en-tête ne doit contenir que les en-têtes nécessaires à un consommateur des services décrits.

Par exemple, un en-tête de projet ne doit pas inclure <stdio.h> sauf si l'une des interfaces de fonction utilise le type FILE * (ou l'un des autres types définis uniquement dans <stdio.h> ). Si une interface utilise size_t , le plus petit en-tête suffisant est <stddef.h> . De toute évidence, si un autre en-tête qui définit size_t est inclus, il n'est pas nécessaire d'inclure également <stddef.h> .

Si les en-têtes sont minimes, le temps de compilation est également réduit au minimum.

Il est possible de concevoir des en-têtes dont le seul but est d'inclure beaucoup d'autres en-têtes. Celles-ci s'avèrent rarement être une bonne idée à long terme, car peu de fichiers source auront besoin de toutes les fonctionnalités décrites par tous les en-têtes. Par exemple, un <standard-ch> pourrait être conçu, incluant tous les en-têtes C standard, car certains en-têtes ne sont pas toujours présents. Cependant, très peu de programmes utilisent réellement les fonctionnalités de <locale.h> ou <tgmath.h> .

Inclure ce que vous utilisez (IWYU)

Le projet Include What You Use de Google, ou IWYU, garantit que les fichiers source incluent tous les en-têtes utilisés dans le code.

Supposons qu'un fichier source source.c inclue un en-tête arbitrary.h qui à son tour inclut freeloader.h , mais que le fichier source utilise aussi explicitement et indépendamment les fonctionnalités de freeloader.h . Tout va bien pour commencer. Ensuite, un jour arbitrary.h est modifié afin que ses clients n'aient plus besoin des fonctionnalités de freeloader.h . Soudain, source.c arrête la compilation, car elle ne répond pas aux critères IWYU. Étant donné que le code dans source.c utilisait explicitement les fonctionnalités de freeloader.h , il aurait dû inclure ce qu'il utilise - il devrait également y avoir eu un #include "freeloader.h" explicite dans le source. ( Idempotency aurait assuré qu'il n'y avait pas de problème.)

La philosophie IWYU maximise la probabilité que le code continue à se compiler, même avec des modifications raisonnables apportées aux interfaces. De toute évidence, si votre code appelle une fonction qui est ensuite supprimée de l'interface publiée, aucune préparation ne peut empêcher des modifications. C'est pourquoi les modifications apportées aux API sont évitées dans la mesure du possible, et les cycles de dépréciation sont multiples sur plusieurs versions, etc.

Ceci est un problème particulier en C ++ car les en-têtes standard sont autorisés à s’inclure. Le fichier source file.cpp peut inclure un en-tête header1.h qui sur une plate-forme inclut un autre en-tête header2.h . file.cpp peut que file.cpp utilise également les fonctionnalités de header2.h . Ce ne serait pas un problème au départ - le code compilerait parce header1.h comprend header2.h . Sur une autre plate-forme, ou une mise à niveau de la plate-forme actuelle, header1.h pourrait être révisé pour ne plus inclure header2.h , puis file.cpp cesserait de compiler en conséquence.

IWYU identifiera le problème et recommandera que header2.h soit inclus directement dans file.cpp . Cela permettrait de continuer à compiler. Des considérations analogues s'appliquent également au code C.

Notation et Divers

Le standard C indique qu'il y a très peu de différence entre les #include <header.h> et #include "header.h" .

[ #include <header.h> ] recherche une séquence de lieux définis par l'implémentation pour un en-tête identifié de manière unique par la séquence spécifiée entre les délimiteurs < et > et provoque le remplacement de cette directive par le contenu entier de l'en-tête. La façon dont les lieux sont spécifiés ou l'en-tête identifié sont définis par la mise en œuvre.

[ #include "header.h" ] provoque le remplacement de cette directive par tout le contenu du fichier source identifié par la séquence spécifiée entre les délimiteurs "…" . Le fichier source nommé est recherché d'une manière définie par l'implémentation. Si cette recherche n'est pas prise en charge ou si la recherche échoue, la directive est traitée comme si elle lisait [ #include <header.h> ]…

Ainsi, la forme de guillemets doubles peut regarder plus d'endroits que la forme entre crochets. La norme spécifie par exemple que les en-têtes standard doivent être inclus entre crochets, même si la compilation fonctionne à la place des guillemets. De même, les normes telles que POSIX utilisent le format entre crochets - et vous devriez aussi le faire. Réserver des en-têtes entre guillemets doubles pour les en-têtes définis par le projet. Pour les en-têtes définis en externe (y compris les en-têtes d'autres projets sur lesquels repose votre projet), la notation entre crochets est la plus appropriée.

Notez qu'il doit y avoir un espace entre #include et l'en-tête, même si les compilateurs n'acceptent aucun espace. Les espaces sont bon marché.

Un certain nombre de projets utilisent une notation telle que:

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

Vous devriez envisager d'utiliser ce contrôle d'espace de noms dans votre projet (c'est probablement une bonne idée). Vous devriez éviter les noms utilisés par les projets existants (en particulier, sys et linux seraient de mauvais choix).

Si vous utilisez ceci, votre code doit être prudent et cohérent dans l'utilisation de la notation.

N'utilisez pas la notation #include "../include/header.h" .

Les fichiers d'en-tête doivent rarement, voire jamais, définir des variables. Bien que vous gardiez les variables globales au minimum, si vous avez besoin d'une variable globale, vous le déclarerez dans un en-tête et le définirez dans un fichier source approprié, et ce fichier source inclura l'en-tête pour vérifier la déclaration et la définition. , et tous les fichiers sources utilisant la variable utiliseront l’en-tête pour le déclarer.

Corollaire: vous ne déclarerez pas de variables globales dans un fichier source - un fichier source ne contiendra que des définitions.

Les fichiers d'en-tête doivent rarement déclarer static fonctions static , à l'exception notable des fonctions static inline qui seront définies dans les en-têtes si la fonction est nécessaire dans plusieurs fichiers source.

  • Les fichiers source définissent des variables globales et des fonctions globales.
  • Les fichiers sources ne déclarent pas l'existence de variables ou de fonctions globales; ils incluent l'en-tête qui déclare la variable ou la fonction.
  • Les fichiers d'en-tête déclarent la variable globale et les fonctions (et les types et autres éléments de support).
  • Les fichiers d'en-tête ne définissent aucune variable ou fonction, à l'exception inline fonctions en inline ( static ).

Références croisées



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow