2016-03-03 36 views
13

Ho un codice semplice, dove i miei funzioni sono dichiarate prima funzione principale del genere:dichiarazione Funzione vs. definizione C

int function1(); 
int function2(); 

int main() { 
    /* ... */ 
    function1(x,y); 
    function2(x,y); 
    /* .... */ 
} 

int function1(int x, float y) { /* ... */ } 
int function2(int x, float y) { /* ... */ } 

E dopo la mia funzione principale ho definizioni di funzioni:

C'è qualche differenza, quando dichiaro le funzioni prima di main in questo modo?

int function1(int x, float y); 
int function2(int x, float y); 

int main() { 
    /* ... */ 
    function1(x,y); 
    function2(x,y); 
    /* .... */ 
} 

int function1(int x, float y) { /* ... */ } 
int function2(int x, float y) { /* ... */ } 
+0

Le tue prime due righe dichiarano funzioni con una firma diversa (nessun parametro) a quelle che hai dichiarato in seguito. Quindi le prime due linee non sono necessarie. Le dichiarazioni di funzione descrivono il nome, il numero e il tipo di parametri e il tipo di reso. Due funzioni possono avere lo stesso nome ma diversi parametri. Non possono differire solo nel tipo di ritorno. – BryanT

+9

@BryanT Questo non è corretto (sarebbe comunque corretto in C++). In C, le parentesi vuote nella dichiarazione della funzione significano che può richiedere un numero qualsiasi di argomenti di qualsiasi tipo. Se vuoi esplicitamente zero argomenti, usa '(void)'; vedi una delle firme standard di 'main':' int main (void) {...} '. – szczurcio

+0

Sono d'accordo. Stavo pensando al C++. Ma non è ancora meglio dichiararli esattamente come saranno definiti? – BryanT

risposta

15

Sì, sono diversi.

Nel primo esempio, si sta solo dicendo al compilatore il nome e il tipo di ritorno della funzione e nulla degli argomenti previsti.

Nel secondo esempio si indica al compilatore la firma completa delle funzioni, sia il tipo restituito che gli argomenti previsti, prima di chiamarli.

Il secondo modulo è praticamente universalmente migliore in quanto consente al compilatore di eseguire un lavoro migliore avvisandolo quando si ha il tipo o il numero di argomenti errati quando si chiama la funzione.

noti inoltre int function() in C è una funzione che può accettare qualsiasi argomenti, non una funzione che accetta non argomenti. Per questo è necessario un esplicito void, cioè int function(void). Questo per lo più salta su quelli che arrivano a C da C++.

Consulta anche: Why does a function with no parameters (compared to the actual function definition) compile?

Per dimostrare perché la prima, forma antiquata è un male in moderna C, il seguente programma viene compilato senza preavviso con gcc -Wall -ansi -pedantic o gcc -Wall -std=c11.

#include<stdio.h> 
int foo(); 

int main(int argc, char**argv) 
{ 
    printf("%d\n", foo(100)); 
    printf("%d\n", foo(100,"bar")); 
    printf("%d\n", foo(100,'a', NULL)); 
    return 0; 
} 

int foo(int x, int y) 
{ 
    return 10; 
} 

UPDATE: M & M portato alla mia attenzione che il mio esempio utilizza int non float per le funzioni. Penso che possiamo essere tutti d'accordo sul fatto che dichiarare int function1() è una cattiva forma, ma la mia affermazione che questa dichiarazione accetta qualsiasi argomento non è del tutto corretta. Vedi la risposta di Vlad per la sezione delle specifiche pertinenti che spiega perché è così.

+0

Dovresti anche menzionare che il primo caso dell'OP è un comportamento non definito, anche se il compilatore non lo diagnostica (a cui non è richiesto) –

+0

Ovviamente il secondo è migliore poiché il primo ha ancora un comportamento indefinito, che tra l'altro significa che sul mio compilatore non riesce a compilare con le impostazioni predefinite. –

2

Nel primo caso, main() esegue promozioni intere su ciascun argomento e promozioni float -01- double. Questi sono chiamati "promozioni argomento predefinito". Quindi probabilmente finirai per chiamare le funzioni in modo non corretto, passando un int e un double dove le funzioni prevedono uno int e un float.

Vedere Default argument promotions in C function calls e le risposte per ulteriori dettagli.

14

Sì, sono diversi; il secondo è corretto, il primo come è il intero errato. È così errato che GCC 5.2.1 rifiuta di compilarlo del tutto.Che funziona per voi, a tutti è solo un fluke:

/* this coupled with */ 
int function1(); 

int main() { 
    /* this */ 
    function1(x, y); 
} 

/* and this one leads to undefined behaviour */ 
int function1(int x, float y) { 
    /* ... */ 
} 

Nel codice precedente, la dichiarazione int function1(); non specifica i tipi di argomenti (non ha un prototipo), che è considerato un obsoleto caratteristica in C11 (e C89, C99 per quella materia) standard. Se viene chiamato questo tipo di funzione, le promozioni degli argomenti predefiniti vengono eseguite sugli argomenti: int viene passato così com'è, ma viene promosso lo float su double.

Poiché la funzione effettiva prevede argomenti di (int, float), non (int, double), ciò comporterà un comportamento non definito. Anche se la tua funzione è prevista a (int, double) ma era un numero intero, oppure dici che l'hai chiamato con function1(0, 0); anziché function(0, 0.0);, il tuo programma avrebbe comunque un comportamento non definito. Fortunatamente GCC 5.2.1 le comunicazioni che la dichiarazione e la definizione di function1 sono contrastanti:

% gcc test.c 
test.c:9:5: error: conflicting types for ‘function1’ 
int function1(int x, float y) { 
    ^
test.c:9:1: note: an argument type that has a default promotion can’t 
    match an empty parameter name list declaration 
int function1(int x, float y) { 
^ 
test.c:1:5: note: previous declaration of ‘function1’ was here 
int function1(); 
    ^
test.c:12:5: error: conflicting types for ‘function2’ 
int function2(int x, float y) { 
    ^
test.c:12:1: note: an argument type that has a default promotion can’t 
    match an empty parameter name list declaration 
int function2(int x, float y) { 
^ 
test.c:2:5: note: previous declaration of ‘function2’ was here 
int function2(); 
    ^

e le uscite del compilatore con codice di errore, mentre il mio tcccompila felicemente, nessuna diagnostica, niente. Produce solo il codice rotto. Lo stesso vale, ovviamente, se si avesse la dichiarazione in un file di intestazione e la definizione in una diversa unità di compilazione che non includesse quella dichiarazione.


Ora, qualora il compilatore non rilevare questo caso, in fase di esecuzione, nulla potrebbe accadere, come previsto per comportamento indefinito.

Ad esempio, supponiamo che gli argomenti siano stati passati in pila; sui processori a 32 bit int e float potrebbero essere contenuti in 4 byte, mentre double potrebbe essere di 8 byte; la chiamata di funzione sarebbe quindi spingere x come int e y come double anche se era float - in totale il chiamante avrebbe spinto 12 byte e il chiamato si aspetta solo 8.

In un altro caso, si supponga che si definirebbe la funzione con 2 numeri interi. Il codice chiamante dovrebbe quindi caricarli in registri interi, ma il chiamante si aspetterebbe un doppio in un registro a virgola mobile. Il registro a virgola mobile potrebbe contenere un valore trap che, una volta eseguito, potrebbe uccidere il programma.

Qual è peggio, il programma potrebbe ora si comportano come previsto , contenente quindi un heisenbug che potrebbe causare problemi quando si ricompilare il codice con una nuova versione di compilatore, o la porta ad un'altra piattaforma.

+0

Tranne che nessun compilatore ti avviserà della forma "sbagliata" senza piegarsi all'indietro per dirglielo. Vedi il programma di esempio nella mia risposta. –

+0

@Antti Haapala Non c'è niente di sbagliato nel primo snippet di codice. –

+3

@VladfromMoscow ha un comportamento indefinito quindi dai miei libri è dannatamente sbagliato –

14

La differenza è che quindi esiste un prototipo di funzione come nel secondo snippet di codice, quindi il compilatore verifica che il numero e i tipi di argomenti corrispondano al numero e ai tipi dei parametri. Il compilatore può emettere un errore in fase di compilazione se troverà un'incoerenza.

Se non esiste un prototipo di funzione come nel primo snippet di codice, il compilatore esegue le promozioni degli argomenti predefiniti su ogni argomento che include le promozioni intere e la conversione di espressioni di tipo float in tipo double. Se dopo queste operazioni il numero e i tipi di argomenti promossi non corrispondono al numero e ai tipi di parametri, il comportamento non è definito.Il compilatore può non essere in grado di emettere un errore perché la definizione della funzione può essere in qualche altra unità di compilazione.

qui ci sono le citazioni importanti dalla C standard (6.5.2.2 chiamate di funzione)

2 Se l'espressione che indica la funzione chiamata ha un tipo che include un prototipo, il numero di argomenti si è d'accordo con il numero di parametri . Ogni argomento deve avere un tipo tale che il suo valore possa essere assegnato a un oggetto con la versione non qualificata del tipo corrispondente al parametro corrispondente.

6 Se l'espressione che indica la funzione chiamata ha un tipo che non include un prototipo, promozioni interi vengono eseguite su ogni argomento, e gli argomenti che hanno a galleggiante sono promosso doppio. Queste sono chiamate promozioni di argomenti predefinite. Se il numero di argomenti non corrisponde al numero di parametri, il comportamento di non è definito. Se la funzione è definita con un tipo che include include un prototipo e il prototipo termina con i puntini di sospensione (, ...) oi tipi di argomenti dopo la promozione non sono compatibili con i tipi dei parametri, , il comportamento è indefinito. Se la funzione è definita con un prototipo e i tipi di argomenti dopo la promozione non sono compatibili con quelli dei parametri dopo la promozione, il comportamento non è definito, tranne nei seguenti casi:

- un tipo promosso è un tipo intero con segno, l'altro tipo promosso è il corrispondente numero intero senza segno e il valore è rappresentabile in entrambi i tipi;

- entrambi i tipi sono puntatori a versioni qualificate o non qualificate di un tipo di carattere o vuoto.

Come per i frammenti di codice, se il secondo parametro aveva il tipo double, il codice sarebbe ben formato. Tuttavia, poiché il secondo parametro ha tipo float ma l'argomento corrispondente verrà promosso a tipo double, il primo snippet di codice presenta un comportamento non definito.