cpp e h / hpp #include: "perché" domanda

1

Perché la fonte include un'intestazione e non il contrario? L'ho cercato su Google, ma ho trovato solo domande sull'uso dei file di intestazione, su come includerli, ma non saprei dire perché è come è. Se l'intestazione è semplicemente la dichiarazione, come fa il compilatore a conoscere solo la definizione?
Ad esempio: prendi foo.cpp , bar.h , bar.cpp . Questo è quello che fanno tutti:
in foo.cpp :
#include "bar.h"
ma bar.cpp non è incluso né in bar.h o foo.cpp . Ecco perché ritengo logico che bar.cpp sia incluso in bar.h e quindi, indirettamente in foo.cpp .

    
posta mireazma 18.02.2015 - 11:37
fonte

4 risposte

5

La compilazione del codice C e C ++ viene eseguita in due passaggi distinti.

Nel primo passaggio, il codice sorgente in un singolo file .c o .cpp viene compilato nel codice oggetto. Durante la compilazione del file foo.cpp , il compilatore deve sapere che bar contiene un metodo doSomething e quali parametri quel metodo si aspetta e quale tipo restituito restituisce, ma il compilatore non ha bisogno di sapere esattamente cosa fa la funzione internamente.
Il compilatore controlla che la chiamata nel codice da foo.cpp corrisponda a come viene dichiarata la funzione (che è ottenuta dall'applicativo # "bar.h"), e quindi fa un'annotazione nel codice oggetto che alla posizione X la funzione% si chiamabar::doSomething.

Nella seconda fase, tutti i file sorgente che compongono l'applicazione sono collegati insieme per creare l'eseguibile finale. A questo punto, il linker tenta di sostituire le annotazioni effettuate dal compilatore nel codice oggetto con l'indirizzo effettivo della funzione corrispondente. Al linker viene detto esplicitamente quali file oggetto deve essere visualizzato.
È solo a questo punto che le definizioni di tutte le funzioni devono essere disponibili.

Quali file sono necessari per creare un'applicazione è tipicamente definito in un file di progetto o in un makefile.

    
risposta data 18.02.2015 - 12:58
fonte
4

La tua domanda indica che non capisci il processo di compilazione (la maggior parte si applica a c e anche a c ++).

L'unità fondamentale della compilazione è un file sorgente; per C ++ la convenzione è usare l'estensione del file .cpp. L'estensione potrebbe essere qualsiasi cosa, ma la maggior parte dei compilatori usa l'estensione per determinare come elaborare il file, es. .cpp significa c ++, .o significa oggetto, .c significa diritto c, ecc. I file sorgente sono complied in file object (nominalmente .obj o .o). I file oggetto sono collegati insieme per creare file eseguibili.

Il compilatore non sa nulla dei file header. C'è un pre-processore che sostituisce le direttive #include con il contenuto del file di intestazione ed elabora altre direttive pre-processore, l'output risultante è ciò che il compilatore vede come input; questo è il motivo per cui le intestazioni sono incluse nei file sorgente e non viceversa.

I file header non sono strettamente necessari per compilare source to object; sono uno strumento per gestire la complessità, organizzare e modulare i file sorgente. Come hanno spiegato le altre risposte, quando foo.cpp deve fare riferimento a qualcosa in bar.cpp, ad esempio baz (), foo.cpp deve avere alcune informazioni descrittive su baz () in modo che possa generare un riferimento che il linker può risolvere.

Si potrebbe semplicemente inserire queste informazioni in foo.cpp, ma ciò duplicherebbe le informazioni in bar.cpp. La soluzione a questa duplicazione consiste nel mettere tutte le informazioni descrittive in un altro file, che chiamiamo un file di intestazione (nominalmente .h o .hpp.) Con bar.h le informazioni che devono essere incluse in foo.cpp e bar.cpp è solo in un file, che viene inserito nel file quando il pre-processore trova la direttiva #include. Ciò diventa inestimabile con l'aumentare della complessità del progetto.

Gran parte del processo di compilazione è nascosto dietro un IDE o un makefile. Compilare un piccolo progetto a mano sarà molto istruttivo per imparare come funziona il processo di compilazione. La prima volta che ho dovuto scrivere a .makefile da zero a scuola tutto il mistero del processo di compilazione è svanito; Ho dovuto imparare tutti i dettagli per svolgere il compito.

La compilazione e il collegamento del tuo esempio sarebbero 3 operazioni, in due fasi:

  • Compila i file sorgente:
    • Compilare foo.cpp in foo.o (ad esempio gcc -c -o foo.o foo.cpp)
    • Compila bar.cpp in bar.o (ad esempio gcc -c -o bar.o bar.cpp)
  • Collega i file oggetto:
    • link foo.o e bar.o in programma (ad esempio gcc -o programma foo.o bar.o)

Durante la fase di compilazione ogni file viene elaborato individualmente. I file oggetto risultanti contengono codice macchina e riferimenti esterni (cose non presenti nel file sorgente, ma in qualche altro file sorgente).

Il passo finale è quello di collegare gli oggetti, questo è quando i riferimenti sono risolti, se si dimentica di includere il file oggetto nel comando link, fallirà con riferimenti esterni non risolti; questo è il passo in cui sono necessarie tutte le informazioni. I moderni compilatori possono gestire tutti questi passaggi con un singolo comando, ma questo nasconde il processo.

    
risposta data 20.02.2015 - 04:35
fonte
1

Se bar.cpp ha incluso bar.h e viceversa, non ci sarebbe motivo di avere quei due come file separati, poiché sarebbero effettivamente identici.

Ma è un punto: tu non hai bisogno della definizione per la maggior parte delle attività. La compilazione di un modulo che utilizza i servizi di bar ha solo bisogno di sapere che bar contiene un metodo doThings() che prende un int e restituisce un int . È non necessario sapere come bar raggiunge questo (in effetti, questo è l'intero punto dei moduli). Pertanto, durante la compilazione di un altro modulo, il compilatore si preoccupa solo di leggere il file di intestazione, ma durante la compilazione di bar stesso legge entrambi i file. In un grande sistema composto da molti moduli, questo consente di risparmiare un enorme sforzo perché ogni riga di codice definizione viene compilata una sola volta e non molte volte.

    
risposta data 18.02.2015 - 11:41
fonte
1

Se sei un compilatore, la vita è facile, purché tu sappia che cosa "assomiglia" a tutto. Ad esempio, 'X' è un numero intero a 64 bit, 'Y' è un puntatore a qualcosa di quel Tipo, 'Z' è un array di questo Tipo e dimensione, e così via. Prendi questo all'estremo e ottieni un precocissimo compilatore Pascal, dove il metodo main deve essere la cosa last nel file sorgente! Tutto il resto deve essere definito per primo. Scrivere codice in questo modo è doloroso, perché l'ordine è naturale per il compilatore ma non per quelli come te e me.

Immettere il file Header (.h).

Descrive tutti i metodi che appaiono nel tuo file .cpp in modo che il compilatore possa "prendere nota" del loro aspetto (noto come "dichiarazioni anticipate") e quindi riconoscerli appena appaiono, ancora, ancora giù nel codice. Altrimenti, deve solo assumere una definizione "predefinita" per qualsiasi metodo e poi lamentarsi come un matto quando trova una firma di impiantazione diversa (alcuni compilatori di Solaris assumono "int methodName (int)" come predefinito, IIRC.

Ma, nonostante tutto questo, non hai ancora bisogno di un file di intestazione.

Puoi fare tutto ciò all'interno del file .cpp stesso.

Ora aggiungi "codice riutilizzabile" nel mix. Altri file .cpp vogliono utilizzare i metodi in il tuo file .cpp, ma il compilatore deve ancora sapere come sono questi metodi ".

Suddividendo le dichiarazioni del metodo in un file di intestazione, stai effettivamente "pubblicando" l'API fornita dal tuo file .cpp. (ADA ha "pacchetto" e "corpo del pacchetto", la maggior parte del mondo Microsoft semplicemente sbiadisce tutti i dettagli di dichiarazione e implementazione in un singolo file, la dll).

Quindi il file di intestazione fornisce una singola dichiarazione dei tuoi metodi, utilizzabile da te e da qualsiasi numero di altri che potrebbero aver bisogno di utilizzare tali metodi. Nota, comunque, che le persone che usano il tuo file Header non hanno bisogno del tuo file .cpp; il codice [oggetto] compilato in questo modo ha sempre "buchi" (puntatori di funzioni non risolti); è il lavoro del linker per unire tutti i punti insieme.

    
risposta data 20.02.2015 - 13:16
fonte

Leggi altre domande sui tag