2013-08-31 14 views
18

So che i file di intestazione contengono dichiarazioni di varie funzioni, strutture ecc. Utilizzate nel file .c che "chiama" lo #include, giusto? Per quanto ho capito, la "separazione dei poteri" si verifica in questo modo:File di intestazione C e compilazione/collegamento

file di intestazione: func.h

  • contiene dichiarazione anticipata della funzione

    int func(int i); 
    

C file sorgente: func.c

  • contiene definizione di una funzione reale

    #include "func.h" 
    
    int func(int i) { 
        return ++i ; 
    } 
    

file sorgente C source.c (il programma "reale"):

#include <stdio.h> 
#include "func.h" 

int main(void) { 
    int res = func(3); 
    printf("%i", res); 
} 

la mia domanda è: visto che il #include è semplicemente una direttiva del compilatore che copia il contenuto di .h nel file in cui è #include, in che modo il file .c sa come eseguire effettivamente la funzione? Tutto quello che ottiene è il int func(int i);, quindi come può effettivamente eseguire la funzione? Come si ottiene l'accesso alla definizione attuale di func? L'intestazione include una sorta di "puntatore" che dice "questa è la mia definizione, laggiù!"?

Come funziona?

+0

Questa è la magia di 'Linker' che risolve le definizioni e garantisce che le cose che hai affermato di esistere durante la compilazione esistano realmente. –

+0

Quando si ha a che fare con i file di intestazione, si potrebbe voler leggere su [include guards] (http://en.wikipedia.org/wiki/Include_guard). –

+0

So di includere le guardie (se non è un tutto) ma le ho omesse per brevità. – Aristides

risposta

20

Uchia Itachi ha dato la risposta. È il linker .

Utilizzando GNU C compiler gcc si compilare un programma di un file, come

gcc hello.c -o hello # generating the executable hello 

Ma la compilazione del programma di due (o più) file come descritto nel tuo esempio, si dovrebbe effettuare le seguenti operazioni:

gcc -c func.C# generates the object file func.o 
gcc -c main.C# generates the object file main.o 
gcc func.o main.o -o main # generates the executable main 

Ogni file oggetto ha simboli esterni (si potrebbe pensare come membri pubblici). Le funzioni sono di default esterne mentre le variabili (globali) sono di default interne. Si potrebbe modificare questo comportamento definendo

static int func(int i) { # static linkage 
    return ++i ; 
} 

o

/* global variable accessible from other modules (object files) */ 
extern int global_variable = 10; 

Quando incontra una chiamata a una funzione, non definito nel modulo principale, il linker cerca tutti i file oggetto (e biblioteche) forniti come input per il modulo in cui è definita la funzione chiamata. Di default probabilmente hai alcune librerie collegate al tuo programma, ecco come puoi usare printf, è già compilato in una libreria.

Se sei veramente interessato, prova qualche programmazione di assemblaggio. Questi nomi sono l'equivalente delle etichette nel codice assembly.

+0

Quindi con GCC il modello è: 1. Usa il flag -c con ogni .c (con definizioni) e .h (con prototipi func) per creare ogni .o 2. Usa -o flag e ogni file .o per creare il exe finale? – Aristides

+0

Sì. l'opzione "-c" è per "compilare", quindi basta compilare il codice oggetto in file oggetto. gcc senza -c riconosce che gli input sono file oggetto, quindi li collega semplicemente usando il linker. E infine, il flag -o è opzionale, viene utilizzato per specificare il nome del file di output dell'eseguibile. –

+2

Questa è davvero una buona risposta, grazie. –

14

È il linker che gestisce tutto ciò. Il compilatore emette solo una sequenza speciale nel file oggetto che dice "Ho questo simbolo esterno func, per favore risolvilo" per il linker. Quindi il linker lo vede e cerca tutti gli altri file e librerie di oggetti per il simbolo.

+0

Ciò significa che verrà cercato tutto il file '.c' nel progetto? –

+0

@LidongGuo Se compili tutti i file sorgente insieme sulla riga di comando, o se crei file oggetto da tutte le fonti e li colleghi tutti insieme, allora sì saranno cercati. Tuttavia, non è fatto automaticamente, devi dire al linker quali file oggetto vuoi collegare, e solo quelli saranno cercati. –

2

L'intestazione fornisce l'accesso non solo ad altri file .c nello stesso programma, ma anche alle librerie che possono essere distribuite in formato binario. La relazione di un file .c in un altro è esattamente la stessa di una libreria che dipende da un altro.

Poiché un'interfaccia di programmazione deve essere in forma di testo, indipendentemente dal formato dell'implementazione, i file di intestazione hanno senso come separazione delle preoccupazioni.

Come altri hanno già menzionato, il programma che risolve chiamate di funzione e accessi tra librerie e sorgenti (unità di traduzione) è chiamato linker.

Il linker non funziona con le intestazioni. Crea solo una grande tabella di tutti i nomi che sono definiti in tutte le unità di traduzione e le librerie, quindi collega quei nomi alle linee di codice che li accedono. L'uso arcaico di C consente persino di chiamare una funzione senza alcuna dichiarazione di implementazione; si presumeva che ogni tipo indefinito fosse uno int.

3

Una dichiarazione di un simbolo senza una definizione all'interno della stessa unità di compilazione indica al compilatore di compilare con un segnaposto per l'indirizzo di quel simbolo in un file oggetto.

Il linker vedrà che è richiesta una definizione per il simbolo e cercherà le definizioni esterne del simbolo nelle librerie e in altri file oggetto.

Se il linker trova una definizione, il segnaposto nel file oggetto originale verrà sostituito con l'indirizzo trovato nell'eseguibile finale.

1

In genere quando si compila un file in questo modo:

gcc -o program program.c 

È davvero sta chiamando un programma pilota, che esegue le seguenti operazioni:

  • pre-elaborazione (se avete chiesto che sia un separato passaggio) utilizzando cpp.
  • compilazione (può essere integrato con pre-elaborazione) utilizzando cc1
  • assemblaggio, utilizzando as (gas, la GNU Assembler).
  • collegamento tramite collect2, che utilizza anche ld (il linker GNU).

In genere, durante le prime 3 tappe, si crea un file semplice oggetto (.o estensione), che viene creato da compilazione di un unità di compilazione (che è un file .c, con il # include e altre direttive sostituito dal il preprocessore).

Il 4 ° stadio è quello che crea l'eseguibile finale. Dopo la compilazione di un'unità, il compilatore segna diversi pezzi di codice come riferimenti che devono essere risolti dal linker. Il lavoro del linker consiste nel cercare tra molte unità di compilazione e risolvere i riferimenti a unità di compilazione esterne.

Problemi correlati