Questa è probabilmente una risposta più dettagliata di quanto tu volessi, ma penso che una spiegazione decente sia giustificata.
In C e C ++, un file sorgente è definito come un'unità di traduzione . Per convenzione, i file di intestazione contengono dichiarazioni di funzioni, definizioni di tipi e definizioni di classi. Le implementazioni effettive delle funzioni risiedono in unità di traduzione, ad esempio file .cpp.
L'idea alla base di ciò è che le funzioni e le funzioni dei membri class / struct vengono compilate e assemblate una volta, quindi altre funzioni possono chiamare quel codice da una posizione senza fare duplicati. Le tue funzioni sono dichiarate come "esterne" implicitamente.
/* Function declaration, usually found in headers. */
/* Implicitly 'extern', i.e the symbol is visible everywhere, not just locally.*/
int add(int, int);
/* function body, or function definition. */
int add(int a, int b)
{
return a + b;
}
Se vuoi che una funzione sia locale per un'unità di traduzione, la definisci come 'statica'. Cosa significa questo? Significa che se includi file sorgente con funzioni extern, otterrai errori di ridefinizione, perché il compilatore incontra la stessa implementazione più di una volta. Quindi, vuoi che tutte le tue unità di traduzione vedano la dichiarazione della funzione ma non il corpo della funzione .
Quindi, come si fa a mescolare tutto alla fine? Questo è il lavoro del linker. Un linker legge tutti i file oggetto generati dalla fase assembler e risolve i simboli. Come ho detto prima, un simbolo è solo un nome. Ad esempio, il nome di una variabile o una funzione. Quando le unità di traduzione che chiamano funzioni o dichiarano tipi non conoscono l'implementazione di tali funzioni o tipi, si dice che quei simboli sono irrisolti. Il linker risolve il simbolo non risolto collegando l'unità di traduzione che contiene il simbolo indefinito insieme a quello che contiene l'implementazione. Uff. Questo vale per tutti i simboli visibili esternamente, siano essi implementati nel tuo codice o forniti da una libreria aggiuntiva. Una libreria è in realtà solo un archivio con codice riutilizzabile.
Ci sono due notevoli eccezioni. Innanzitutto, se hai una piccola funzione, puoi renderla in linea. Ciò significa che il codice macchina generato non genera una chiamata di funzione esterna, ma è letteralmente concatenato sul posto. Poiché di solito sono piccoli, le dimensioni generali non contano. Potete immaginare che siano statici nel modo in cui funzionano. Quindi è sicuro implementare funzioni incorporate nelle intestazioni. Le implementazioni di funzioni all'interno di una definizione di classe o di struttura sono spesso integrate automaticamente dal compilatore.
L'altra eccezione sono i modelli. Poiché il compilatore deve visualizzare l'intera definizione del tipo di modello durante l'istanza, non è possibile disaccoppiare l'implementazione dalla definizione come con le funzioni autonome o le classi normali. Bene, forse questo è possibile ora, ma ottenere il supporto diffuso del compilatore per la parola chiave "export" ha richiesto molto, molto tempo. Pertanto, senza il supporto per "esportazione", le unità di traduzione ottengono le loro copie locali di tipi e funzioni di modelli istanziati, in modo simile al funzionamento delle funzioni inline. Con il supporto per "export", non è così.
Per le due eccezioni, alcune persone trovano "più bello" inserire le implementazioni di funzioni inline, funzioni con template e tipi con template in file .cpp e quindi #includere il file .cpp. Non importa se si tratta di un'intestazione o di un file sorgente; al preprocessore non importa ed è solo una convenzione.
Un breve riepilogo dell'intero processo dal codice C ++ (diversi file) a un eseguibile finale:
- Il preprocessore viene eseguito, che analizza tutte le direttive che iniziano con un '#'. La direttiva #include concatena il file incluso con un valore inferiore, ad esempio. Fa anche macro-sostituzione e incolla token.
- Il compilatore effettivo viene eseguito sul file di testo intermedio dopo la fase del preprocessore ed emette il codice assembler.
- L' assemblatore viene eseguito sul file di assieme ed emette codice macchina, in genere viene chiamato file oggetto e segue il formato eseguibile binario del sistema operativo in questione. Ad esempio, Windows utilizza PE (formato eseguibile portatile), mentre Linux utilizza il formato ELF Unix System V, con estensioni GNU. In questa fase, i simboli sono ancora contrassegnati come indefiniti.
- Infine, viene eseguito il linker . Tutte le fasi precedenti sono state eseguite su ciascuna unità di traduzione in ordine. Tuttavia, la fase del linker funziona su tutti i file oggetto generati che sono stati generati dall'assemblatore. Il linker risolve i simboli e fa molta magia come la creazione di sezioni e segmenti, che dipende dalla piattaforma di destinazione e dal formato binario. I programmatori non sono tenuti a saperlo in generale, ma sicuramente aiutano in alcuni casi.
Ancora una volta, questo è stato sicuramente più di quello che hai chiesto, ma spero che i dettagli nitidi ti aiutino a vedere il quadro più ampio.