2015-06-16 13 views
17

So che questa è una domanda comune ma non riesco ancora a capirlo.Il file di intestazione è incluso solo una volta nell'intero programma?

In un programma C o C++ generato da più file di origine e di intestazione differenti, sarà ogni file di intestazione essere incluso solo una volta in tutta la codice quando vengono utilizzate le guardie di intestazione?

Qualcuno mi ha detto in precedenza che un file di intestazione (con protezioni incluse) verrà incluso solo una volta in una unità di traduzione ma più volte nell'intero codice. È vero?

Se viene incluso solo una volta sull'intero codice, quando un file desidera includerlo e il preprocessore rileva che è già stato incluso, in che modo quel file che desidera utilizzarlo sa dove si trova nel codice che era precedentemente incluso ?

+8

* "un file di intestazione (con include guardie) verrà incluso solo una volta in un'unità di traduzione ma più volte nell'intero codice. ?"* Sì. Non una volta per programma, è (al massimo) una volta per unità di traduzione. – CoryKramer

+6

Con le protezioni incluse, l'intestazione può ancora essere inclusa più volte anche in una unità di traduzione, ma la maggior parte di essa (la parte all'interno delle guardie) verrà saltata dopo la prima volta. Il preprocessore non si cura di dove è stato precedentemente incluso ... solo se la macro della guardia è stata ancora definita (cosa che accade la prima volta che viene inclusa se le protezioni sono configurate correttamente). – Dmitri

+0

Grazie. Adoro questo sito :) risposte super veloci e ottime risposte. – Engineer999

risposta

16

Un "file di intestazione" viene effettivamente inserito dal pre-processore prima dell'inizio della compilazione. Basti pensare alla semplice "sostituzione" della sua direttiva #include.

La guardia ...

#ifndef MY_HEADER_H 
#define MY_HEADER_H 

.... 

#endif 

... viene eseguita dopo la sostituzione. Quindi, l'intestazione può essere effettivamente inclusa più volte, ma la parte "protetta" del testo viene passata al compilatore solo una volta dal preprocessore.

Quindi, se nell'intestazione sono presenti definizioni di generazione del codice, queste verranno ovviamente incluse nel file oggetto dell'unità di compilazione (ovvero "modulo"). Se la stessa intestazione è #include ded in più moduli, questi appariranno più volte.

Per le definizioni static, questo non è un problema, in quanto questi non saranno visibili oltre il modulo (file scope). Per le definizioni globali del programma, questo è diverso e causerà l'errore "più definizioni".

Nota: questo è principalmente per C. Per C++, ci sono differenze significative, come le classi, ecc. Aggiungono ulteriore complessità a cosa/quando sono consentiti più oggetti globali.

+3

potresti anche menzionare il #pragma non standard una volta, vale la pena conoscere la sua esistenza – dlavila

+3

@dlavila: non l'ho fatto per uno scopo. Non si dovrebbe fare affidamento su di esso, quindi devi comunque adire le guardie. Inoltre, questo potrebbe sfidare le librerie come boost che usano intenzionalmente più include. (Non devo attenermi sempre allo standard, ma il collegamento, una volta, ha troppi vantaggi e può causare molti problemi). – Olaf

+4

vale la pena conoscere la sua esistenza comunque, non come una sostituzione, ma a volte può essere molto utile, ad esempio: se sei sicuro che sia supportato (ragazzi SWE della tua azienda te l'hanno detto) e stai lavorando in un progetto con migliaia di intestazioni (il tempo di compilazione può essere molto più veloce). – dlavila

5

Il file di intestazione verrà incluso una volta per unità di traduzione, sì. Può essere incluso più volte per programma, poiché ogni unità di traduzione viene gestita separatamente per il processo di compilazione. Sono riuniti durante il processo di collegamento per formare un programma completo.

+0

Cosa succederà se abbiamo le definizioni di funzione nel file di intestazione. Non ci saranno molte definizioni della stessa funzione in tutto il programma che non è consentita? – Engineer999

+0

@ Engineer999 Le definizioni di funzione non sono in genere incluse nei file di intestazione, ad eccezione delle funzioni inline – Random832

+0

Non sono implicitamente in linea le funzioni se sono definite all'interno di un corpo di classe in un file di intestazione (C++, naturalmente). Come sono gestite le molteplici definizioni allora? – Engineer999

6

Un file di intestazione con include guards appropriato sarà incluso solo una volta per unità di traduzione. A rigor di termini, potrebbe essere incluso più volte, ma le parti tra il preprocessore #ifndef e #endif verranno saltate nelle inclusioni successive. Se fatto correttamente, questo dovrebbe essere tutto (o la maggior parte) del file.

Un'unità di traduzione di solito corrisponde a un "file sorgente", anche se alcune implementazioni oscure possono utilizzare una definizione diversa. Se un file sorgente compilato separatamente include la stessa intestazione, il preprocessore ha non c'è modo di sapere che è già stato incluso da un altro file o che qualsiasi altro file faceva parte dello stesso progetto.

Si noti che quando si arriva a collegare insieme più file di origine (unità di traduzione) in un unico binario, è possibile riscontrare problemi con più definizioni se l'intestazione non consiste solo di dichiarazioni, modelli, definizioni di funzioni che sono contrassegnati inline o definizioni di variabili statiche. Per evitare ciò, è necessario dichiarare le funzioni nell'intestazione e definirle in un file sorgente separato, che si collega insieme agli altri file sorgente.

20

Questo è il processo:

source   header source header header 
    \   /  \ | //
    \  /  \ | //
    PREPROCESSOR   PREPROCESSOR 
     |      | 
     V      V 
preprocessed code  preprocessed code 
     |      | 
    COMPILER    COMPILER 
     |      | 
     V      V 
    object code    object code 
      \   /
       \  /
       \  /
       LINKER 
        | 
        V 
       executable 

preelaborazione

#include è per questo primo passo. Indica al preprocessore di elaborare il file specificato e inserire il risultato nell'output.

Se A include B e C, e B include C, l'uscita del preprocessore per A includerà il testo elaborato di C due volte.

Questo è un problema, poiché genererà dichiarazioni duplicate. Un rimedio consiste nell'utilizzare le variabili del preprocessore per tenere traccia se il codice sorgente è stato incluso (aka header guards).

#ifndef EXAMPLE_H 
#define EXAMPLE_H 

// header contents 

#endif 

La prima volta, EXAMPLE_H è indefinito, e il preprocessore si valutano il contenuto all'interno del blocco ifndef/endif. La seconda volta salterà quel blocco. Quindi l'output elaborato cambia e le definizioni sono incluse solo una volta.

Questo è così comune che esiste una direttiva non standard implementato da alcuni compilatori che è più breve e non richiede la scelta di una variabile preprocessore unico:

#pragma once 

// header contents 

si può capire come portatile si desidera che il C/Codice C++ e quale intestazione utilizzare.

Le protezioni delle intestazioni garantiscono che i contenuti di ciascun file di intestazione siano presenti al massimo una volta nel codice preelaborato per un'unità di traduzione.

Compilazione

il compilatore genera codice macchina dal C pre-elaborato/C++.

In genere, i file di intestazione includono solo dichiarazioni, non le definizioni effettive (ovvero le implementazioni). Il compilatore include una tabella dei simboli per tutto ciò che attualmente manca una definizione.

Linking

Il linker combina i file oggetto. Corrisponde alle definizioni (alias implementazioni) con i riferimenti alla tabella dei simboli.

È possibile che due file oggetto forniscano la definizione e che il linker ne esegua uno.Questo succede se hai inserito il codice eseguibile nelle intestazioni. Questo generalmente non accade in C, ma succede molto spesso in C++, a causa dei template.

Il "codice" di testa, se le dichiarazioni o definizioni, è incluso più volte in tutti i file oggetto, ma il linker si fonde tutto questo insieme, in modo che sia presente solo una volta nel file eseguibile. (Sono escluse le funzioni inline, che sono presenti più volte.)

+0

Ottima risposta, ma non dovresti dire "Generalmente, i file di intestazione includono solo ** dichiarazioni **, non le effettive implementazioni." poiché le definizioni sono generalmente sinonimi di implementazioni in c/C++? –

+1

@BryanShaw, sì, grazie per l'aiuto. –

Problemi correlati