C Language
Crea e includi i file di intestazione
Ricerca…
introduzione
Nella moderna C, i file di intestazione sono strumenti cruciali che devono essere progettati e utilizzati correttamente. Consentono al compilatore di eseguire il controllo incrociato delle parti compilate in modo indipendente di un programma.
Le intestazioni dichiarano tipi, funzioni, macro ecc. Che sono necessari ai consumatori di una serie di servizi. Tutto il codice che utilizza una di queste strutture include l'intestazione. Tutto il codice che definisce queste strutture include l'intestazione. Ciò consente al compilatore di verificare che gli usi e le definizioni corrispondano.
introduzione
Ci sono un certo numero di linee guida da seguire quando si creano e si usano i file di intestazione in un progetto C:
Idemopotence
Se un file di intestazione è incluso più volte in un'unità di traduzione (TU), non dovrebbe interrompere le build.
Self-contenimento
Se hai bisogno delle strutture dichiarate in un file di intestazione, non dovresti includere esplicitamente nessun altro header.
minimalità
Non dovresti essere in grado di rimuovere alcuna informazione da un'intestazione senza causare fallimenti di build.
Includi cosa usi (IWYU)
Di maggiore preoccupazione per C ++ rispetto a C, ma comunque importante anche in C. Se il codice in una TU (chiamiamolo
code.c
) utilizza direttamente le caratteristiche dichiarate da un'intestazione (chiamatelo"headerA.h"
), quindicode.c
dovrebbe#include "headerA.h"
direttamente, anche se la TU include un'altra intestazione (chiamiamola"headerB.h"
) che al momento include"headerA.h"
.
Occasionalmente, ci potrebbero essere buoni motivi per rompere una o più di queste linee guida, ma entrambi dovresti essere consapevole del fatto che stai infrangendo la regola ed essere consapevole delle conseguenze di ciò prima di romperlo.
idempotence
Se un particolare file di intestazione è incluso più di una volta in un'unità di traduzione (TU), non dovrebbero esserci problemi di compilazione. Questo è chiamato 'idempotence'; le tue intestazioni dovrebbero essere idempotenti. Pensa a quanto sarebbe difficile la vita se dovessi assicurarti che #include <stdio.h>
stato incluso solo una volta.
Esistono due modi per ottenere l'idempotence: header guards e #pragma once
directive.
Guardie di testa
Le protezioni dell'intestazione sono semplici e affidabili e conformi allo standard C. Le prime righe senza commento in un file di intestazione dovrebbero essere del formato:
#ifndef UNIQUE_ID_FOR_HEADER
#define UNIQUE_ID_FOR_HEADER
L'ultima riga di non commento deve essere #endif
, facoltativamente con un commento dopo di esso:
#endif /* UNIQUE_ID_FOR_HEADER */
Tutto il codice operativo, comprese altre #include
direttive, dovrebbe essere tra queste linee.
Ogni nome deve essere unico. Spesso, viene utilizzato uno schema di nomi come HEADER_H_INCLUDED
. Alcuni codici meno #ifndef BUFSIZ
utilizzano un simbolo definito come intestazione (ad esempio #ifndef BUFSIZ
in <stdio.h>
), ma non è affidabile come un nome univoco.
Un'opzione consisterebbe nell'utilizzare un hash MD5 (o altro) generato per il nome della guardia dell'intestazione. Evitare di emulare gli schemi utilizzati dalle intestazioni di sistema che utilizzano frequentemente i nomi riservati all'implementazione - i nomi iniziano con un trattino basso seguito da un altro carattere di sottolineatura o da una lettera maiuscola.
Il #pragma once
direttiva
In alternativa, alcuni compilatori supportano la direttiva #pragma once
che ha lo stesso effetto delle tre linee mostrate per le guardie di intestazione.
#pragma once
I compilatori che supportano #pragma once
includono MS Visual Studio e GCC e Clang. Tuttavia, se la portabilità è un problema, è preferibile utilizzare le protezioni intestazione o utilizzare entrambe. I compilatori moderni (quelli che supportano C89 o successivi) devono ignorare, senza commenti, pragma che non riconoscono ("Qualsiasi pragma di questo tipo che non è riconosciuto dall'implementazione viene ignorato") ma le vecchie versioni di GCC non erano così indulgenti.
Self-contenimento
Le intestazioni moderne dovrebbero essere autarchiche, il che significa che un programma che deve utilizzare le funzionalità definite da header.h
può includere quell'intestazione ( #include "header.h"
) e non preoccuparsi se altre intestazioni devono essere incluse per prime.
Raccomandazione: i file di intestazione dovrebbero essere autonomi.
Regole storiche
Storicamente, questo è stato un argomento moderatamente controverso.
Nel corso di un altro millennio, gli standard AT & T Indian Hill C e gli standard di codifica hanno dichiarato:
I file di intestazione non devono essere nidificati. Il prologo per un file di intestazione dovrebbe, quindi, descrivere quali altre intestazioni devono essere
#include
d perché l'intestazione sia funzionale. In casi estremi, in cui è necessario includere un numero elevato di file di intestazione in diversi file di origine, è accettabile inserire tutti i#include
s comuni in un file di inclusione.
Questa è l'antitesi del self-contenimento.
Regole moderne
Tuttavia, da allora, l'opinione ha teso nella direzione opposta. Se un file sorgente deve utilizzare le strutture dichiarate da un'intestazione header.h
, il programmatore dovrebbe essere in grado di scrivere:
#include "header.h"
e (soggetto solo ad avere i percorsi di ricerca corretti impostati sulla riga di comando), tutti gli header necessari per i prerequisiti saranno inclusi da header.h
senza bisogno di ulteriori intestazioni aggiunte al file sorgente.
Ciò fornisce una migliore modularità per il codice sorgente. Protegge inoltre la fonte dall'enigma "indovina perché questa intestazione è stata aggiunta" che si verifica dopo che il codice è stato modificato e violato per un decennio o due.
Gli standard di codifica Goddard Space Flight Center (GSFC) della NASA per C sono uno degli standard più moderni, ma ora è un po 'difficile da rintracciare. Dichiara che le intestazioni dovrebbero essere autonome. Fornisce anche un modo semplice per garantire che le intestazioni siano autonome: il file di implementazione per l'intestazione dovrebbe includere l'intestazione come prima intestazione. Se non è autonomo, quel codice non verrà compilato.
La motivazione fornita da GSFC include:
§2.1.1 L'intestazione include la logica
Questo standard richiede che l'intestazione di un'unità contenga le istruzioni
#include
per tutte le altre intestazioni richieste dall'intestazione dell'unità. Inserire#include
per l'intestazione dell'unità prima nel corpo dell'unità consente al compilatore di verificare che l'intestazione contenga tutte le istruzioni#include
richieste.
Un disegno alternativo, non consentito da questo standard, non consente di
#include
istruzioni#include
nelle intestazioni; tutti gli#includes
sono fatti nei file del corpo. I file di intestazione dell'unità devono quindi contenere istruzioni #ifdef che controllano che le intestazioni richieste siano incluse nell'ordine corretto.
Un vantaggio del design alternativo è che l'elenco
#include
nel file body è esattamente l'elenco delle dipendenze necessario in un makefile e questo elenco viene controllato dal compilatore. Con la progettazione standard, è necessario utilizzare uno strumento per generare l'elenco delle dipendenze. Tuttavia, tutti gli ambienti di sviluppo consigliati dalla filiale forniscono uno strumento del genere.
Uno svantaggio principale del design alternativo è che se l'elenco di intestazioni richiesto di un'unità cambia, ogni file che utilizza quell'unità deve essere modificato per aggiornare l'elenco di istruzioni
#include
. Inoltre, l'elenco di intestazioni richiesto per un'unità di libreria del compilatore può essere diverso su target diversi.
Un altro svantaggio della progettazione alternativa è che i file di intestazione della libreria del compilatore e altri file di terze parti devono essere modificati per aggiungere le istruzioni
#ifdef
richieste.
Quindi, l'auto-contenimento significa che:
- Se un'intestazione
header.h
bisogno di una nuova intestazione nidificataextra.h
, non è necessario controllare ogni file sorgente che utilizzaheader.h
per vedere se è necessario aggiungereextra.h
. - Se un'intestazione
header.h
non deve più includere un'intestazione specificanotneeded.h
, non è necessario controllare ogni file sorgente che utilizzaheader.h
per vedere se è possibile rimuoverenotneeded.h
modo sicuro (ma vedere Includi ciò che si usa . - Non è necessario stabilire la sequenza corretta per includere le intestazioni pre-requisito (che richiede un ordinamento topologico per eseguire correttamente il lavoro).
Controllo di auto-contenimento
Vedi Collegamento a una libreria statica per uno script chkhdr
che può essere utilizzato per testare l'idempotenza e l'autocontenimento di un file di intestazione.
minimalità
Le intestazioni sono un meccanismo di verifica della coerenza fondamentale, ma dovrebbero essere il più possibile ridotte. In particolare, ciò significa che un'intestazione non dovrebbe includere altre intestazioni solo perché il file di implementazione avrà bisogno delle altre intestazioni. Un'intestazione deve contenere solo le intestazioni necessarie per un consumatore dei servizi descritti.
Ad esempio, un'intestazione di progetto non dovrebbe includere <stdio.h>
meno che una delle interfacce di funzione non utilizzi il tipo FILE *
(o uno degli altri tipi definiti esclusivamente in <stdio.h>
). Se un'interfaccia utilizza size_t
, l'intestazione più piccola che è sufficiente è <stddef.h>
. Ovviamente, se è inclusa un'altra intestazione che definisce size_t
, non è necessario includere anche <stddef.h>
.
Se le intestazioni sono minime, mantiene anche il tempo di compilazione al minimo.
È possibile ideare intestazioni il cui unico scopo è includere molte altre intestazioni. Queste raramente risultano essere una buona idea a lungo termine perché pochi file sorgente avranno effettivamente bisogno di tutte le funzionalità descritte da tutte le intestazioni. Ad esempio, potrebbe essere ideato un <standard-ch>
che includa tutti gli header C standard - con attenzione dato che alcune intestazioni non sono sempre presenti. Tuttavia, pochissimi programmi utilizzano effettivamente le funzionalità di <locale.h>
o <tgmath.h>
.
Includi cosa usi (IWYU)
Il progetto Includi cosa utilizzi di Google o IWYU garantisce che i file di origine includano tutte le intestazioni utilizzate nel codice.
Supponiamo che un file sorgente source.c
includa un'intestazione arbitrary.h
che a sua volta include casualmente freeloader.h
, ma il file sorgente utilizza anche esplicitamente e in modo indipendente le strutture da freeloader.h
. Tutto va bene per cominciare. Quindi un giorno arbitrary.h
viene cambiato in modo che i suoi clienti non freeloader.h
più bisogno delle funzionalità di freeloader.h
. All'improvviso source.c
interrompe la compilazione perché non soddisfa i criteri IWYU. Poiché il codice in source.c
utilizzava esplicitamente le funzionalità di freeloader.h
, avrebbe dovuto includere ciò che utilizzava - avrebbe dovuto esserci un esplicito #include "freeloader.h"
nella sorgente. (L' idempionalità avrebbe assicurato che non c'era un problema.)
La filosofia IWYU massimizza la probabilità che il codice continui a compilare anche con modifiche ragionevoli apportate alle interfacce. Chiaramente, se il tuo codice chiama una funzione che viene successivamente rimossa dall'interfaccia pubblicata, nessuna quantità di preparazione può impedire che le modifiche diventino necessarie. Questo è il motivo per cui le modifiche alle API vengono evitate quando possibile e perché ci sono cicli di deprecazione su più versioni, ecc.
Questo è un problema particolare in C ++ perché le intestazioni standard possono essere incluse tra loro. Il file di origine file.cpp
potrebbe includere un'intestazione header1.h
che su una piattaforma include un'altra intestazione header2.h
. file.cpp
potrebbe risultare utilizzare anche le funzionalità di header2.h
. Inizialmente questo non sarebbe un problema: il codice verrebbe compilato perché header1.h
include header2.h
. Su un'altra piattaforma, o un aggiornamento della piattaforma corrente, header1.h
potrebbe essere rivisto in modo che non includa più header2.h
, e quindi file.cpp
smetterebbe di compilare come risultato.
IWYU individuerebbe il problema e raccomanderebbe che header2.h
fosse incluso direttamente in file.cpp
. Ciò garantirebbe che continui a compilare. Considerazioni analoghe si applicano anche al codice C.
Notazione e Miscellanea
Lo standard C dice che c'è ben poca differenza tra le #include <header.h>
e #include "header.h"
.
[
#include <header.h>
] ricerca una sequenza di luoghi definiti dall'implementazione per un'intestazione identificata univocamente dalla sequenza specificata tra i delimitatori<
e>
e causa la sostituzione di tale direttiva con l'intero contenuto dell'intestazione. Come vengono specificati i posti o l'intestazione identificata è definita dall'implementazione.
[
#include "header.h"
] causa la sostituzione di quella direttiva con l'intero contenuto del file sorgente identificato dalla sequenza specificata tra i delimitatori"…"
. Il file di origine denominato viene cercato in un modo definito dall'implementazione. Se questa ricerca non è supportata, o se la ricerca fallisce, la direttiva viene rielaborata come se leggesse [#include <header.h>
] ...
Pertanto, la forma con doppia citazione può apparire in più punti rispetto alla forma con parentesi angolare. Lo standard specifica per esempio che le intestazioni standard dovrebbero essere incluse tra parentesi angolari, anche se la compilazione funziona se si utilizzano invece le virgolette doppie. Allo stesso modo, standard come POSIX usano il formato parentesi angolare - e dovresti farlo anche tu. Prenota intestazioni a doppia quotazione per intestazioni definite dal progetto. Per le intestazioni definite esternamente (comprese le intestazioni di altri progetti su cui si basa il progetto), la notazione a parentesi angolare è più appropriata.
Nota che dovrebbe esserci uno spazio tra #include
e l'intestazione, anche se i compilatori non accetteranno spazio. Gli spazi sono economici.
Un numero di progetti utilizza una notazione come:
#include <openssl/ssl.h>
#include <sys/stat.h>
#include <linux/kernel.h>
Dovresti considerare se usare quel controllo del namespace nel tuo progetto (è probabilmente una buona idea). Dovresti evitare i nomi usati dai progetti esistenti (in particolare, sia sys
che linux
sarebbero scelte sbagliate).
Se lo usi, il tuo codice dovrebbe essere attento e coerente nell'uso della notazione.
Non usare la notazione #include "../include/header.h"
.
I file di intestazione dovrebbero raramente definire le variabili. Sebbene manterrai le variabili globali al minimo, se hai bisogno di una variabile globale, la dichiarerai in un'intestazione e la definirai in un file sorgente adatto, e quel file di origine includerà l'intestazione per verificare la dichiarazione e la definizione e tutti i file sorgente che usano la variabile useranno l'intestazione per dichiararlo.
Corollario: non dichiarerai le variabili globali in un file sorgente: un file sorgente conterrà solo definizioni.
I file di intestazione dovrebbero dichiarare raramente funzioni static
, con la notevole eccezione delle funzioni static inline
che saranno definite nelle intestazioni se la funzione è necessaria in più di un file sorgente.
- I file di origine definiscono le variabili globali e le funzioni globali.
- I file di origine non dichiarano l'esistenza di variabili o funzioni globali; includono l'intestazione che dichiara la variabile o la funzione.
- I file di intestazione dichiarano variabile globale e funzioni (e tipi e altro materiale di supporto).
- I file di intestazione non definiscono variabili o funzioni eccetto
static
funzioniinline
(static
).
Riferimenti incrociati
- Dove documentare le funzioni in C?
- Elenco di file di intestazione standard in C e C ++
- È in
inline
senzastatic
oextern
mai utile in C99? - Come utilizzare
extern
per condividere le variabili tra i file di origine? - Quali sono i vantaggi di un percorso relativo come
"../include/header.h"
per un'intestazione? - Ottimizzazione dell'inclusione dell'intestazione
- Dovrei includere ogni intestazione?