2015-04-10 18 views
65

Oggi, mentre lavoravo con una libreria personalizzata, ho trovato uno strano comportamento. Un codice di libreria statico conteneva una funzione di debug main(). Non era all'interno di un flag #define. Quindi è presente anche in biblioteca. E viene utilizzato il collegamento a un altro programma che conteneva il vero main().
Quando entrambi sono collegati insieme, il linker non ha generato un errore di dichiarazione multipla per main(). Mi stavo chiedendo come potrebbe accadere.Come viene compilato ed eseguito questo programma C con due funzioni principali?

Per rendere più semplice, ho creato un programma di esempio, che simula lo stesso comportamento:

$ cat prog.c 
#include <stdio.h> 
int main() 
{ 
     printf("Main in prog.c\n"); 
} 

$ cat static.c 
#include <stdio.h> 
int main() 
{ 
     printf("Main in static.c\n"); 
} 

$ gcc -c static.c 
$ ar rcs libstatic.a static.o 
$ gcc prog.c -L. -lstatic -o 2main 
$ gcc -L. -lstatic -o 1main 

$ ./2main 
Main in prog.c 
$ ./1main 
Main in static.c 

Come funziona il "2main" find binario che main per eseguire?

Ma la compilazione e due insieme dà un errore di dichiarazione multipla:

$ gcc prog.c static.o 
static.o: In function `main': 
static.c:(.text+0x0): multiple definition of `main' 
/tmp/ccrFqgkh.o:prog.c:(.text+0x0): first defined here 
collect2: ld returned 1 exit status 

Qualcuno può spiegare questo comportamento?

+1

Se si esegue 'nm' nella libreria statica che contiene il primo' main', mostra che il simbolo 'main' è definito (presente e non con un' U' accanto ad esso)? – anol

+0

il collegamento dinamico cerca solo i simboli necessari e rilascia il principale in libstatic.a – tristan

+2

Altri hanno le risposte, ma vorrei commentare che il codice che stai compilando è in violazione della "One Definition Rule" (ODR), che significa comportamento non definito. Capita semplicemente di vedere che aspetto ha questo comportamento indefinito per un compilatore. –

risposta

57

Citando ld (1):

The linker will search an archive only once, at the location where it is specified on the command line. If the archive defines a symbol which was undefined in some object which appeared before the archive on the command line, the linker will include the appropriate file(s) from the archive.

Quando si collegano 2main, principale simbolo viene risolto prima che raggiunga ld -lstatic, perché ld lo raccoglie da prog.o.

Quando si collega 1main, si ha un main non definito dal momento in cui arriva a -lstatic, quindi cerca l'archivio per main.

Questa logica si applica solo agli archivi (librerie statiche), non agli oggetti regolari. Quando si collegano prog.o e static.o, tutti i simboli di entrambi gli oggetti sono inclusi incondizionatamente, quindi si ottiene un errore di definizione duplicato.

+14

Si noti che questo comportamento è specifico del compilatore (o, beh, specifico del linker): A Il linker Microsoft cercherebbe nella sua linea di comando tutti i file e le librerie di oggetti e genera un errore se un simbolo è duplicato. – Medinoc

17

Quando si collega una libreria statica (.a), il linker cerca solo l'archivio se ci sono stati simboli non definiti rintracciati finora. Altrimenti, non guarda affatto all'archivio. Quindi, il tuo caso 2main, non guarda mai l'archivio in quanto non ha simboli indefiniti per creare l'unità di traduzione.

Se si include una semplice funzione in static.c:

#include <stdio.h> 
void fun() 
{ 
     printf("This is fun\n"); 
} 
int main() 
{ 
     printf("Main in static.c\n"); 
} 

e chiamare da prog.c, quindi linker saranno costretti a guardare l'archivio per trovare il simbolo fun e si otterrà lo stesso multiplo principale errore di definizione come linker troverebbe il simbolo duplicato main ora.

Quando si compila direttamente i file oggetto (come in gcc a.o b.o), il linker non ha alcun ruolo qui e tutti i simboli sono inclusi per creare un singolo binario e ovviamente ci sono simboli duplicati.

La riga di fondo è che il linker guarda l'archivio solo se mancano i simboli. Altrimenti, è buono come non collegarsi con nessuna libreria.

+0

Ho modificato static.c come hai detto e chiamato da prog.c. Ora il linker ha felicemente mostrato errori di definizione multipli. Quindi mi sono chiesto se questo è vero anche per le librerie condivise. Ma non sembra. '$ gcc -c -fPIC static.c $ gcc -shared -o libsharedlib.so static.o $ gcc -L. -lsharedlib prog.c -o 2mainso $ ./2mainso Questo è divertente principale in prog.c ' Si può spiegare anche questo? – MrPavanayi

+1

Non sono esperto di librerie condivise, ma afaik, la ricerca di simboli di libreria condivisa è simile a quella statica ad eccezione di "entry point" per il simbolo richiesto che è nella libreria condivisa è identificato e memorizzato dal linker. Quindi sembra che quando si ha .so che contiene ('fun' e un duplicato' main'), il linker memorizza solo "entry point" per 'fun'. Questa è comunque la mia interpretazione basata su ciò che 'nm' mostra (solo' U' c'è 'divertimento' oltre alle chiamate libc). –

1

Dopo che il linker carica qualsiasi file oggetto, cerca le librerie per simboli non definiti. Se non ce ne sono, quindi non è necessario leggere librerie. Poiché main è stato definito, anche se trova una main in ogni libreria, non c'è motivo di caricarne una seconda.

I linker hanno tuttavia comportamenti radicalmente diversi. Ad esempio, se la libreria includeva un file oggetto con entrambi main() e foo() al suo interno, e foo non era definito, molto probabilmente si otterrebbe un errore per un simbolo definito in più main().

I linker moderni (tautologici) ometteranno i simboli globali da oggetti irraggiungibili - ad es. AIX. I linker di vecchio stile come quelli trovati su Solaris ei sistemi Linux si comportano ancora come i linker Unix degli anni '70, caricando tutti i simboli da un modulo oggetto, raggiungibili o meno. Questa può essere una fonte di orrore gonfio e tempi di collegamento eccessivi.

Anche la caratteristica dei linker * nix è che essi cercano efficacemente una libreria solo una volta per ogni volta che viene elencata. Ciò pone una richiesta al programmatore di ordinare le librerie sulla riga di comando a un linker o in un file make, oltre a scrivere un programma. Non richiedere un elenco ordinato di librerie non è moderno. I sistemi operativi meno recenti spesso disponevano di linker che cercavano ripetutamente tutte le librerie fino a quando un passaggio non risolveva un simbolo.

Problemi correlati