2010-09-21 17 views
24

stavo leggendo wikipedia sul C/dichiarazioni di C++ per prototipi e io sono confuso:Scopo del C/C++ Prototipi

Wikipedia dice: "Con l'inclusione del prototipo di funzione, si informa il compilatore che la funzione 'fac' prende un argomento intero e si abilita il compilatore a catturare questi tipi di errori. "

e utilizza il sotto come esempio:

#include <stdio.h> 

/* 
    * If this prototype is provided, the compiler will catch the error 
    * in main(). If it is omitted, then the error will go unnoticed. 
    */ 
int fac(int n);    /* Prototype */ 

int main(void) {    /* Calling function */ 
    printf("%d\n", fac()); /* ERROR: fac is missing an argument! */ 
    return 0; 
} 

int fac(int n) {    /* Called function */ 
    if (n == 0) 
     return 1; 
    else 
     return n * fac(n - 1); 
} 

Ma la definizione della funzione della funzione chiamata include già tutte le informazioni che il prototipo dice al compilatore, quindi perché non può il compilatore dedurre questo informazioni dalla definizione della funzione chiamata poiché contengono identiche dichiarazioni/informazioni lettera per lettera?

Cosa mi manca? Sembra un lavoro extra senza guadagni ovvi.

Modifica: Grazie ragazzi. Immaginavo che i compilatori fossero multi-pass. Sono viziato in linguaggi correnti come Python. Ha senso dal momento che è così vecchio da aver bisogno di alcuni kludges per fare le cose con precisione in un unico passaggio. Mi sembra più ovvio ora. Apparentemente richiede una conoscenza abbastanza intima di come il compilatore collega e compila.

+7

Si noti che questo articolo di wikipedia contiene cose sbagliate. Puoi vedere nella pagina di discussione come alcuni ragazzi non si lasceranno correggere. Ho rinunciato a questo. –

+0

Ecco perché definisco le funzioni prima che vengano chiamate (nello stesso file sorgente, comunque). Elimina la necessità della dichiarazione separata, anche se significa che il mio codice legge "all'indietro". –

risposta

16

I prototipi consentono di separare l'interfaccia dall'implementazione.

Nel tuo esempio, tutto il codice si trova in un unico file e si potrebbe facilmente spostare la definizione fac() su dove si trova il prototipo e rimuovere il prototipo.

I programmi del mondo reale sono composti da più file .cpp (ovvero unità di compilazione), frequentemente compilati e collegati in librerie prima di essere collegati in forma eseguibile finale. Per progetti su larga scala di questo tipo, i prototipi vengono raccolti in file .h (ovvero file di intestazione), in cui l'intestazione è inclusa in altre unità di compilazione in fase di compilazione per avvisare il compilatore dell'esistenza e delle convenzioni di chiamata della funzionalità nella libreria. In questi casi, la definizione della funzione non è disponibile per il compilatore, quindi i prototipi (ovvero le dichiarazioni) servono come una sorta di contratto che definisce le capacità e i requisiti della libreria.

3

Il compilatore C elabora i file di origine dall'alto verso il basso. Le funzioni che appaiono dopo il non vengono prese in considerazione quando si risolvono i tipi di argomenti. Quindi, nel tuo esempio, se main() si trovasse nella parte inferiore del file, non sarebbe necessario un prototipo per fac() (poiché la definizione di fac() sarebbe già stata visualizzata dal compilatore durante la compilazione di main()).

26

due motivi:

  1. Il compilatore legge il file top-to-bottom. Se viene utilizzato in , che è superiore a fac e non esiste alcun prototipo, il compilatore non sa come verificare che quella chiamata venga eseguita correttamente, poiché non ha ancora raggiunto la definizione di fac.

  2. È possibile suddividere un programma C o C++ in più file. fac può essere definito in un file completamente diverso dal file che il compilatore sta attualmente elaborando, e quindi ha bisogno di sapere che quella funzione esiste da qualche parte, e come dovrebbe essere chiamata.

Si noti che i commenti nel esempio che hai postato si applicano solo a C. In C++, che l'esempio sarà sempre produrre un errore, anche se il prototipo è omesso (anche se produrrà un errore diverso a seconda se il prototipo esiste o no). In C++, tutte le funzioni devono essere definite o prototipate prima di essere utilizzate.

In C, è possibile omettere il prototipo e il compilatore consente di chiamare la funzione con qualsiasi numero di argomenti (compreso lo zero) e assumerà un tipo di ritorno di int. Ma solo perché non ti urla durante la compilazione non significa che il programma funzionerà correttamente se non chiami la funzione nel modo giusto. Ecco perché è utile prototipare in C: così il compilatore può ricontrollare a tuo nome.

La filosofia alla base di C e C++ che motiva questo tipo di funzionalità è che si tratta di linguaggi di livello relativamente basso. Non fanno molto hand-holding e non fanno molto se non controllano i tempi di esecuzione. Se il tuo programma fa qualcosa di sbagliato, si bloccherà o si comporterà in modo bizzarro. Pertanto, le lingue incorporano funzionalità come questa che consentono al compilatore di identificare determinati tipi di errori in fase di compilazione, in modo che sia più facile individuarli e risolverli.

+0

Nel caso # 2 non includesse o qualcosa del genere indichi quale file trovare la funzione reale in? In python devi semplicemente importare il modulo e quindi fare riferimento alla funzione come module.function, non c ha spazi dei nomi che possono essere referenziati in questo modo? – pythonnewbie

+0

Come risolve il # 1? Nulla nel prototipo dice al compilatore che fac chiama main e/o quella fac principale delle chiamate? – pythonnewbie

+0

Nel caso n. 2, cosa pensi che gli include contengano? Contengono prototipi! Ma se stai scrivendo la tua funzione 'fac', * devi * fornire il prototipo, sia che si trovi in ​​un file incluso separato o nello stesso file sorgente. Nel caso # 1, (presumo che ti riferisci alla risposta di Mystagogue qui), il problema non è che ci sia qualcosa di sbagliato in una dipendenza ricorsiva, è che se hai una dipendenza ricorsiva, non c'è modo di ordinare le due funzioni in modo tale che il compilatore legga la definizione di entrambi prima di elaborare una chiamata su entrambi. –

4

Il motivo più importante per il prototipo è la risoluzione delle dipendenze circolari. Se "main" può chiamare "fac" e "fac" chiama "main", allora avrai bisogno di un prototipo per risolverlo.

+0

Questo, insieme al collegamento a una libreria già compilata, sono in realtà le due ragioni per cui è necessaria la dichiarazione.Tutti gli altri sono cosmetici/preferenze. – codechimp

1

Oltre a tutte le buone risposte già fornite, pensaci: se tu fossi un compilatore, e il tuo compito era quello di tradurre il codice sorgente nel linguaggio macchina, e tu (essendo il compilatore doveroso che sei) potresti solo leggere una fonte codice riga per riga: come leggeresti il ​​codice che hai incollato se non ci fosse un prototipo? Come sapresti che la chiamata alla funzione è valida e non un errore di sintassi? (sì, potresti fare un appunto e controllare alla fine se tutto è uguale, ma questa è un'altra storia).

Un altro modo di vedere le cose (questa volta come un essere umano): supponiamo di non hanno la funzione definita come prototipo, è il suo codice sorgente disponibile. Sai, comunque, che nella libreria che ti è stata data dalla tua compagna c'è il codice macchina che, una volta eseguito, restituisce un determinato comportamento previsto. Che carino. Ora, come fareste sapere al compilatore che tale chiamata di funzione è valida se non esiste un prototipo che lo dica "hey dude credetemi, esiste una funzione denominata tale e tale che prende parametri e restituisce qualcosa"?

So che è un modo molto, molto, molto semplicistico di pensarci. Aggiungere intenzionalità a pezzi di software è probabilmente un brutto segno, vero?

4

C e C++ sono due lingue diverse e in questo caso particolare c'è un'enorme differenza tra i due. Dal contenuto della domanda presumo che si sta parlando di C.

#include <stdio.h> 
int main() { 
    print(5, "hi"); // [1] 
} 
int print(int count, const char* txt) { 
    int i; 
    for (i = 0; i < count; ++i) 
     printf("%s\n", txt); 
} 

Questo è un programma C corretta che fa quello che ci si può aspettare: stampa 5 linee dicendo "ciao" in ognuno di loro. Il linguaggio C trova la chiamata in [1] assume che print è una funzione che restituisce int e accetta un numero sconosciuto di argomenti (sconosciuto al compilatore, noto al programmatore), il compilatore assume che la chiamata sia corretta e continua la compilazione. Poiché la definizione della funzione e la chiamata corrispondono, il programma è ben formato.

Il problema è che quando il compilatore analizza la riga in [1] non può eseguire alcun tipo di controllo di tipo, poiché non sa quale sia la funzione. Se quando scriviamo questa riga sbagliamo l'ordine degli argomenti e scriviamo print("hi", 5); il compilatore accetterà comunque la riga, in quanto non ha conoscenza a priori di print. Poiché la chiamata non è corretta anche se il codice viene compilato, fallirà in seguito.

Dichiarando la funzione in anticipo, si fornisce al compilatore le informazioni necessarie per verificare nel luogo di chiamata. Se la dichiarazione è presente e si commette lo stesso errore, il compilatore rileverà l'errore e ti informerà del tuo errore.

In C++, d'altra parte, il compilatore non presumerà che la chiamata sia corretta e che in realtà richieda a di fornire una dichiarazione della funzione prima della chiamata.