2014-11-26 27 views
5

Ho questa cosa strana:Funzione extern durante il collegamento?

in un file1.c c'è

extern void foo(int x, int y); 
.. 
.. 

int tmp = foo(1,2); 

nel progetto sono riuscito a trovare solo in questo foo():

in file2.c:

int foo(int x, int y, int z) 
{ 
.... 
} 

in file2.h:

int foo(int x, int y, int z); 

file2.h non è incluso in file1.c (questo è il motivo per cui è stato scritto da extern, credo).

questo progetto si compila bene, penso che sia perché in file1.c foo() verrà cercato solo durante il collegamento, ho ragione?

ma la mia vera domanda è: perché il collegamento è di grande aiuto? dopo tutto, non esiste una funzione come foo con 2 parametri .... e io sono in c .. quindi non c'è sovraccarico ..

quindi cosa sta succedendo?

+0

La dichiarazione 'extern' è ridondante se il file di intestazione è incluso. Ma non fa male. – Gene

+0

NON è incluso il file di intestazione – user1047069

+0

Quindi è un programma mal strutturato, che è facile in C. Potrebbe essere l'autore ad usare l'esterno come misura temporanea e averlo dimenticato. O è un trucco a causa di alcuni conflitti causati dall'inclusione dell'intera intestazione. – Gene

risposta

7

Poiché non c'è sovraccarico, il compilatore C non decora i nomi delle funzioni. Il linker trova in file2.c un riferimento alla funzione foo e in file1.c trova una funzione foo. Non può sapere che i loro elenchi di parametri non corrispondono e li usano felicemente.

Ovviamente, quando la funzione foo esegue il valore di z è inutile e il comportamento del programma diventa imprevedibile da quel momento in poi.

+0

ma perché, se scrivo: extern void foo (void); e quindi usarlo: pippo (1,2,3); quindi non passa la compilazione? – user1047069

+0

@ user1047069 Poiché il prototipo "extern void foo (void)" e la chiamata 'foo (1,2,3)' si trovano nello stesso file sorgente. Il compilatore vede subito alla chiamata che non corrisponde al prototipo che dice che la funzione non accetta argomenti. –

0

Prima di tutto

extern void foo(int x, int y); 

significa esattamente la stessa cosa di

void foo(int x, int y); 

Il primo è solo un modo eccessivamente esplicito di scrivere la stessa cosa. extern non ha alcun altro scopo qui. È come scrivere auto int x; anziché int x, significa la stessa cosa.


Nel tuo caso, il modulo "pippo" (che si chiama file2) contiene il prototipo di funzione, nonché la definizione. Questa è la corretta progettazione del programma in C. Ciò che file1.c dovrebbe fare è #include il foo.h.

Per ragioni sconosciute, chiunque abbia scritto file1.c non l'ha fatto. Invece stanno solo dicendo "altrove nel progetto, c'è questa funzione, non importa della sua definizione, che è gestita altrove".

Questa è una cattiva pratica di programmazione. file1.c non deve preoccuparsi di come le cose sono definite altrove: questa è la programmazione spaghetti che crea un accoppiamento stretto inutile tra il chiamante e il modulo. C'è anche la possibilità che la funzione effettiva non corrisponda al prototipo locale, nel qual caso si spera che si verifichino errori di linker. Ma non ci sono garanzie.

Il codice deve essere fissato in questo modo:

file1.c

#include "foo.h" 
... 
int tmp = foo(1,2); 

foo.h

#ifndef FOO_H 
#define FOO_H 

int foo(int x, int y, int z); 

#endif 

foo.c

#include "foo.h" 

int foo(int x, int y, int z) 
{ 
.... 
} 
1

Chiamata di una funzione con il numero errato (o tipi) di argomenti è a n errore. Lo standard richiede l'implementazione per rilevare alcuni, ma non tutti.

Quello che lo standard chiama un'implementazione, è in genere un compilatore con un linker separato (e alcune altre cose), dove un compilatore traduce singole unità di traduzione (cioè un file sorgente preelaborato) in file oggetto, che in seguito vengono collegati insieme. Mentre lo standard non si distingue tra loro, i suoi autori lo hanno scritto con l'impostazione tipica in mente.

C11 (n1570) 6.5.2.2 "le chiamate di funzione", P2:

Se l'espressione che indica la funzione chiamata ha un tipo che include un prototipo, il numero di argomenti deve essere 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 del parametro corrispondente.

Questo è in una sezione "vincoli", cioè, l'applicazione (in questo caso, che è il compilatore) deve lamentare e può interrompere definizione se un "deve" requisito viene violata.

Nel tuo caso, c'era un prototipo visibile, quindi gli argomenti della chiamata di funzione devono corrispondere a con il prototipo.

Requisiti simili si applicano per una definizione di funzione con una dichiarazione di prototipo nell'ambito; se la tua definizione di funzione non corrisponde al prototipo, il tuo compilatore deve dirlo. In altre parole, se si assicura che tutte le chiamate a una funzione e la definizione di quella funzione rientrino nello scopo dello stesso prototipo, viene indicato se vi è una mancata corrispondenza. Ciò può essere garantito se il prototipo si trova in un file di intestazione che è incluso da tutti i file con chiamate a quella funzione e dal file che contiene la sua definizione. Utilizziamo i file di intestazione con i prototipi proprio per questo motivo.

Nel codice visualizzato, questa verifica viene ignorata fornendo un prototipo non corrispondente e non includendo l'intestazione file2.h.

Ibid. p9:

Se la funzione è definita con un tipo non compatibile con il tipo (dell'espressione) puntato dall'espressione che denota la funzione chiamata, il comportamento non è definito.

Un comportamento non definito significa che il compilatore è libero di presupporre che non si verifichi e che non è necessario rilevare se lo fa.

E infatti, sulla mia macchina, i file oggetto generato dal file 2.c (Ho inserito un return 0; per avere un qualche corpo di funzione), non differire se rimuovo uno degli argomenti della funzione, il che significa che il file oggetto non contiene alcuna informazione sugli argomenti e quindi un compilatore vedendo solo file2 .o e file1.c non ha alcuna possibilità di rilevare la violazione.

Hai detto sovraccarico, quindi cerchiamo di compilare file2.c (con due e tre argomenti) come C++ e guardare i file oggetto:

$ g++ file2_three_args.cpp -c 
$ g++ file2_two_args.cpp -c 
$ nm file2_three_args.o 
00000000 T _Z3fooiii 
$ nm file2_two_args.o 
00000000 T _Z3fooii 

Funzione foo ha i suoi argomenti incorporato nel simbolo creato per questo (un processo chiamato nome mangling), il file oggetto porta effettivamente alcune informazioni sui tipi di funzione. Di conseguenza, otteniamo un errore in fase di collegamento:

$ cat file1.cpp 
extern void foo(int x, int y); 

int main(void) { 
     foo(1,2); 
} 
$ g++ file2_three_args.o file1.cpp 
In function `main': 
file1.cpp:(.text+0x19): undefined reference to `foo(int, int)' 
collect2: error: ld returned 1 exit status 

Questo comportamento sarebbe anche consentito per un'implementazione C, interruzione di traduzione è una manifestazione valida di comportamento non definito in fase di compilazione o collegamento tempo.

Il modo in cui l'overloading in C++ viene in genere eseguito in realtà consente i controlli al momento del collegamento. Quella C non ha il supporto integrato per l'overloading delle funzioni, e che il comportamento non è definito nei casi in cui il compilatore non può vedere le discrepanze di tipo, consente di generare simboli per funzioni senza informazioni di tipo.

Problemi correlati