2013-04-18 18 views
5

Con questo codice:Domande su Prototipi C funzione e compilazione

int main(){ 
    printf("%f\n",multiply(2)); 
    return 0; 
} 

float multiply(float n){ 
    return n * 2; 
} 

Quando provo a compilare ottengo un avvertimento: " '% f' aspetta 'doppio', ma l'argomento è di tipo 'int'" e due errori: "i tipi in conflitto per" moltiplicare "", "la dichiarazione implicita precedente di" moltiplicare "era qui."

Domanda 1: Sto indovinando che è perché, dato il compilatore non è a conoscenza della funzione di 'moltiplicare' quando si imbatte per la prima volta, si inventerà un prototipo, e inventò prototipi assumo sempre 'int' viene restituito e preso come parametro. Quindi il prototipo inventato sarebbe "int moltiplicare (int)", e quindi gli errori. È corretto?

Ora, il codice precedente non verrà compilato. Tuttavia, se rompo il codice in due file in questo modo:

#file1.c 
int main(){ 
    printf("%f\n",multiply(2)); 
    return 0; 
} 

#file2.c 
float multiply(float n){ 
    return n * 2; 
} 

ed eseguire "gcc file1.c file2.c -o file" sarà ancora dare un avvertimento (che è in attesa di printf doppia, ma è sempre int) ma gli errori non verranno più visualizzati e verranno compilati.

Domanda 2: Come mai quando rompo il codice in 2 file che compila?

Domanda 3: Una volta eseguito il programma sopra (la versione divisa in 2 file), il risultato è che 0.0000 è stampato sullo schermo. Come mai? Immagino che il compilatore abbia inventato di nuovo un prototipo che non corrisponde alla funzione, ma perché viene stampato 0? E se cambio il printf ("% f") in printf ("% d") stampa un 1. Ancora una spiegazione di cosa sta succedendo dietro le quinte?

Grazie mille in anticipo.

risposta

4

Quindi il prototipo inventato sarebbe "int moltiplicare (int)", e quindi gli errori. È corretto?

Assolutamente.Questo viene fatto per la retrocompatibilità con pre-ANSI C che mancava di prototipi di funzione, e tutto ciò che era dichiarato senza un tipo era implicitamente int. Il compilatore compila il tuo main, crea una definizione implicita di int multiply(int), ma quando trova la vera definizione, scopre la menzogna e te ne parla.

Come mai quando rompo il codice in 2 file che compila?

Il compilatore non scopre la bugia del prototipo, perché compila un file alla volta: si presume che multiply prende un int, e restituisce un int nel vostro main, e non trova alcuna contraddizione in multiply.c. L'esecuzione di questo programma produce comunque un comportamento indefinito.

Una volta eseguito il programma sopra (la versione divisa in 2 file) il risultato è che sullo schermo viene stampato 0,0000.

Questo è il risultato di un comportamento non definito sopra descritto. Il programma verrà compilato e collegato, ma poiché il compilatore ritiene che multiply utilizzi uno int, non converte mai 2 in 2.0F e multiply non lo scoprirà mai. Analogamente, il valore errato calcolato mediante il raddoppio di come float nella propria funzione multiply verrà considerato nuovamente come.

+0

Ha senso. L'unico punto ancora non chiaro è il motivo per cui il compilatore non troverà la menzogna/contraddizione con 2 file. Dopotutto continuerà a creare il prototipo "int multiply (int)" mentre è in esecuzione attraverso main.c, e una volta arrivato al secondo file troverà la dichiarazione di funzione "float multiply (float)", che contraddice chiaramente il precedente prototipo. –

+2

@DanielS Buon punto! Ho modificato la risposta per citare il motivo: in sostanza, il compilatore compila ogni file separatamente, una fonte alla volta. Questo è diverso, ad esempio, da Java, che considera tutti i file contemporaneamente. Con C e C++, ogni unità di traduzione (che è un nome di fantasia per un file '.c') viene compilata separatamente, quindi il linker riunisce i risultati insieme. Nel momento in cui il linker entra in gioco, tuttavia, tutte le informazioni sul tipo sono scomparse: i linker funzionano su un livello molto più basso di byte, offset e indirizzi, quindi non possono nemmeno rilevare una discrepanza. – dasblinkenlight

+1

@DanielS: il compilatore funziona con un file alla volta; mentre lavora su 'file1.c', non sa nulla dei contenuti di' file2.c' e viceversa. Quando si costruisce 'file1.c', sa solo che' multiply' non è stato dichiarato prima dell'uso. Quando si costruisce 'file2.c', non si sa che' moltiplicare' viene utilizzato in un'altra unità di traduzione senza una dichiarazione appropriata nell'ambito. –

1

Domanda 1: Sì, hai ragione. Se non c'è prototipo di funzione, il tipo predefinito è int

Domanda 2: Quando si compila questo codice come un unico file, il compilatore vedere che c'è già funzione denominata multiply e ha un tipo diverso da quello supposto (double anziché int). Quindi la compilazione non funziona.

Quando si separa questo in due file, il compilatore crea due file .o. Nel primo si suppone che la funzione multiply() si troverà in un altro file. Quindi il linker collega entrambi i file in un binario e, in base al nome multiply, inserisce la chiamata di float multiply() sul posto di int multiply() supposto dal compilatore nel primo file .o.

Domanda 3: Se leggete int2 come float, si otterrà un numero molto piccolo (~ 1/2^25), così dopo che si moltiplica per 2 e rimane ancora troppo piccolo per il formato %f. Ecco perché vedi 0.00000.

1

Una funzione non specificata ha un tipo di ritorno int (ecco perché viene visualizzato l'avviso, il compilatore pensa che restituisca un numero intero) e un numero sconosciuto di argomenti non specificati.

Se si interrompe il progetto in più file, basta dichiarare un prototipo di funzione prima di chiamare le funzioni dagli altri file e tutto funzionerà correttamente.

1

Question1:

Quindi il prototipo inventato sarebbe "int moltiplicare (int)", e quindi alle errori. È corretto?

Non exactelly sì, perché dipende della vostra Cx (C89, C90, C99, ...)

per i valori di ritorno delle funzioni, prima di C99 è stato esplicitamente speci fi cato che se nessuna dichiarazione funzione era visibile il traduttore ne ha fornito uno. Queste dichiarazioni implicite insolute ad un tipo di ritorno di int

Giustificazione dal C Standard (6.2.5 pag 506)

Prima di C90 non c'erano prototipi di funzione. Gli sviluppatori si aspettavano che fossero in grado di scambiare argomenti che avevano versioni firmate e non firmate dello stesso tipo intero. Dovendo lanciare un argomento, se il parametro nella definizione della funzione aveva una diversa firma, veniva considerato come in contrasto con il sistema easy-going di controllo dei tipi di C e un piccolo invadente . L'introduzione dei prototipi non ha completamente risolto con la questione dell'interscambiabilità degli argomenti. La notazione dei puntini di sospensione specifica che non si sa nulla dell'eleganza 1590 non fornisce alcun tipo di argomento previsto per le informazioni. Allo stesso modo, per i valori di ritorno della funzione , prima di C99 era esplicitamente specificato che se non era visibile alcuna dichiarazione di funzione, il traduttore ne aveva fornito uno. Queste dichiarazioni implicite hanno impostato automaticamente un tipo restituito di int. Se la funzione effettiva ha restituito il tipo unsigned int, tale dichiarazione di default potrebbe aver restituito un risultato imprevisto. Molti sviluppatori di avevano un atteggiamento casuale verso le dichiarazioni di funzione. Il resto di noi di deve convivere con le conseguenze del comitato non che vogliono rompere tutto il codice sorgente che hanno scritto. La intercambiabilità dei valori di ritorno di funzione è ora un punto controverso, perché C99 richiede che una dichiarazione di funzione essere visibile al punto di chiamata (una dichiarazione predefinito non è più disponibile)

Domanda 2:

Come mai quando rompo il codice in 2 file che compila?

che compilerà e sarà trattato come indicato nella prima questione exactelly stesso